CanDeactivate changing the window history - angular2-routing

I am facing some issue with canDeactivate() whenever it returns false it is changing the window history as when it became true and if i hit the back button. I am navigating to some other URL or out of the app itself.
Please help me

Here is the issue, but it still isn't fixed.
As a workaround, you can manually put the active url back to the history:
export class CanDeactivateGuard implements CanDeactivate<any> {
constructor(
private readonly location: Location,
private readonly router: Router
) {}
canDeactivate(component: any, currentRoute: ActivatedRouteSnapshot): boolean {
if (myCondition) {
const currentUrlTree = this.router.createUrlTree([], currentRoute);
const currentUrl = currentUrlTree.toString();
this.location.go(currentUrl);
return false;
} else {
return true;
}
}
}

The issue is still present with Angular routing and below is what worked for me.
Trick is to use this.router.navigate([currentUrl], { skipLocationChange: true });
Full code:
export class CanDeactivateGuard implements CanDeactivate<any> {
constructor(
private location: Location,
private router: Router
) { }
canDeactivate(component: any,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot): boolean {
if(canDeactivateCondition) {
return true;
} else {
const currentUrl = currentState.url;
if (this.location.isCurrentPathEqualTo(currentUrl)) {
// https://github.com/angular/angular/issues/13586
this.router.navigate([currentUrl], { skipLocationChange: true });
} else {
// A browser button has been clicked or location.back()/forward() invoked. Restore browser history
history.pushState(null, null, location.href);
}
return false;
}
}
}

This is a know bug in Angular: https://github.com/angular/angular/issues/13586
There is a lot a workaround proposed but all of them seem to break other parts.
Appears to be fixed now in Angular 12.1.2: https://github.com/angular/angular/issues/13586#issuecomment-881627456

Related

How to set icon of a button in ViewModel?

I have an icon button and i need to change the icon once user presses the button. I used to change it in the content page before, now if i dont set the value at the top there is no icon what so ever and if i set it in my load method the picture wont check, but if i set it the the top i cant change it either.
private string _playPauseButton = IconsFont.Volume2;
public string PlayPauseButton
{
get { return _playPauseButton; }
set
{
_playPauseButton = value;
NotifyPropertyChanged(nameof(_playPauseButton));
}
}
public ViewModel()
: base(listenCultureChanges: true)
{
Task.Run(async () => await LoadAllDataForDictionary()).Wait();
PlayPauseCommand = new Command(() => StartOrStopPlaying());
}
private void StartOrStopPlaying()
{
if (!viewDisabled)
{
var paused = LangUpDictionaryPlayer.PlayPause();
if (paused)
{
LangUpDictionaryPlayer.SetTrack(_word.Id);
_playPauseButton = IconsFont.AudioPause;
}
else
{
_playPauseButton = IconsFont.Volume2;
}
}
}
Also
what difference would i make by doing this
public string PlayPauseButton
{
set
{
_playPauseButton = value;
NotifyPropertyChanged();
}
get => _playPauseButton;
}
INsted of what i have?
Solved it by using the Public in the code
PlayPauseButton = IconsFont.AudioPause;

FreshMVVM - best way to open a page from a child ContentView that doesn't inherit from FreshBasePageModel

The following code shows 2 examples of an OpenPage Command. The one in MainPageModel works since it derives directly from FreshBasePageModel. However, the second OpenPage call in the ChildPageModel won't work (or compile). I don't want to pass the parent model all around. So how, using FreshMVVM, do I open a new page from the ChildPageModel (and have the back button work, etc)?
public class MainPageModel : FreshBasePageModel
{
public Command OpenPage
{
get
{
return new Command(() =>
{
CoreMethods.PushPageModel<NewPageModel>();
});
}
}
public ChildPageModel ChildPageModel { get; set; }
}
public class ChildPageModel
{
public Command OpenPage
{
get
{
return new Command(() =>
{
// ??????
CoreMethods.PushPageModel<NewPageModel>();
});
}
}
}
You should also make the ChildPageModel inherit from FreshBasePageModel. All PageModels should inherit from FreshBasePageModel
I make a simple example with three pages (MainPage, SecondPage, ThirdPage). You could download the source file of FreshMVVMDemo folder from HitHub.
https://github.com/WendyZang/Test.git
If you want to open a new page, you could add command in the child page.
#region Commands
public Command GotoPageCommand
{
get
{
return new Command(async () =>
{
await CoreMethods.PushPageModel<ThirdPageModel>(); //replace the ThirdPageModel with the page you want to open
});
}
}
#endregion
If you want to go back, add the command like below.
#region Commands
public Command GoBackSecondCommand
{
get
{
return new Command(async () =>
{
//await CoreMethods.PopPageModel(); //go back to main page
await CoreMethods.PushPageModel<SecondPageModel>(); //Go back to third page
});
}
}
#endregion
The following code will accomplish this...
var page = FreshPageModelResolver.ResolvePageModel<MainPageModel>();
var model = page.GetModel() as MainPageModel;
var navService = FreshMvvm.FreshIOC.Container.Resolve<IFreshNavigationService>();
await navService.PushPage(page, null);

How to call actual service instead of local JSON in Getting Started Demos complete example (search,sort,page,and filter)

Working with complete table example (with sorting, filtering and pagination) that simulates a server call using a JSON constant.
I am trying to make it call a real API that returns JSON instead of a local constant.
I was successful in changing the code from the demo from using countries to suppliers. For example countries.ts is suppliers.ts, country.ts is supplier.ts, and country.service is supplier.service.
That works with no problems, but I want to remove the countries.ts (suppliers.ts in my case), export the JSON and replace it with a http.get call to a local API service.
Here's a sample of a working code from the API service that I am trying to call:
getSuppliers(): Observable<SupplierVM[]> {
return this.http.get<SupplierVM[]>(apiUrl+'supplier')
.pipe(
tap(heroes => console.log('fetched Suppliers')),
catchError(this.handleError('getSuppliers', []))
);
}
Here is a sample of a working call from within an Angular component:
allSuppliers:Observable<SupplierVM[]>;
this.allSuppliers=this.api.getSuppliers();
This is the method that does the work in the demo (the only difference is that I am using suppliers instead of countries)
private _search(): Observable<SearchResult> {
const {sortColumn, sortDirection, pageSize, page, searchTerm} = this._state;
//1. sort
let suppliers = sort(SUPPLIERS, sortColumn, sortDirection);
//2. filter
suppliers = suppliers.filter(country => matches(country, searchTerm/*, this.pipe*/));
const total = suppliers.length;
//3. paginate
suppliers = suppliers.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);
return of({suppliers, total});
}
This works when I call suppliers from the import statement, but I want to replace the suppliers from the sort method to something like this.allSuppliers (kind of like the sample method call above).
//1. sort
let suppliers = sort(this.allSuppliers, sortColumn, sortDirection);
Everything works when using local imported constant composed of JSON and should work just the same when calling actual service because the JSON response is the exact same.
I had a go at this and ended up implementing a switchable server-side/client-side sorting/pagination/filtering system (Angular 12).
Base Service: All services are derived from this, even ones without sorting/pagination/filtering.
import { HttpClient, HttpParams } from "#angular/common/http";
import { Observable } from "rxjs";
export abstract class BaseService<T> {
constructor(protected _http: HttpClient, protected actionUrl: string) {
}
getAll(params?: HttpParams): Observable<T[]> {
if (params)
return this._http.get(this.actionUrl, { params: params }) as Observable<T[]>;
return this._http.get(this.actionUrl) as Observable<T[]>;
}
getSingle(id: number, params?: HttpParams): Observable<T> {
if (params)
return this._http.get(`${this.actionUrl}/${id}`, { params: params }) as Observable<T>;
return this._http.get(`${this.actionUrl}/${id}`) as Observable<T>;
}
push(content: any, id: number) {
if (id === 0)
return this._http.post<any>(`${this.actionUrl}`, content);
return this._http.post<any>(`${this.actionUrl}/${id}`, content);
}
post(content: any) {
return this._http.post<any>(`${this.actionUrl}`, content);
}
}
Sortable Service: Services with a sortable/paginated/filtered result set are always derived from this base service. Derived services need to implement their own matching algorithm (_matches()).
import { HttpClient } from "#angular/common/http";
import { BehaviorSubject, Observable, Subject } from "rxjs";
import { BaseService } from "./base.service";
import { SearchResult, SearchState } from "#app/common/_models";
import { SortDirection } from '#app/common/_directives';
import { debounceTime, delay, switchMap, tap } from "rxjs/operators";
export abstract class SortableService<T> extends BaseService<T> {
private _loading$ = new BehaviorSubject<boolean>(true);
private _search$ = new Subject<void>();
private _items$ = new BehaviorSubject<T[]>([]);
private _total$ = new BehaviorSubject<number>(0);
protected _state: SearchState = {
page: 1,
pageSize: 10,
searchTerm: '',
sortColumn: '',
sortDirection: ''
};
constructor(protected _http: HttpClient, protected actionUrl:string) {
super(_http, actionUrl);
this._search$.pipe(
tap(() => this._loading$.next(true)),
debounceTime(200),
switchMap(() => this._search()),
tap(() => this._loading$.next(false))
).subscribe(response => {
this._items$.next(response.result);
this._total$.next(response.total);
});
this._search$.next();
}
get items$() { return this._items$.asObservable(); }
get total$() { return this._total$.asObservable(); }
get loading$() { return this._loading$.asObservable(); }
get page() { return this._state.page; }
get pageSize() { return this._state.pageSize; }
get searchTerm() { return this._state.searchTerm; }
set page(page: number) { this._set({ page }); }
set pageSize(pageSize: number) { this._set({ pageSize }); }
set searchTerm(searchTerm: string) { this._set({ searchTerm }); }
set sortColumn(sortColumn: string) { this._set({ sortColumn }); }
set sortDirection(sortDirection: SortDirection) { this._set({ sortDirection }); }
private _set(patch: Partial<SearchState>) {
Object.assign(this._state, patch);
this._search$.next();
}
protected _compare(v1: string | number | boolean | T[keyof T], v2: string | number | boolean | T[keyof T]) {
return v1 < v2 ? -1 : v1 > v2 ? 1 : 0;
}
private _getValue<T>(obj: T, column: string) {
var parts = column.split('.');
var result : any = obj;
for (let part of parts) {
for (let res in result) {
if (res === part)
result = result[res];
}
}
return result;
}
protected _sort(items: T[], column: string, direction: string): T[] {
if (direction === '' || column === '')
return items;
return [...items].sort((a, b) => {
var aa = this._getValue(a, column);
var bb = this._getValue(b, column);
const res = aa === undefined ? -1 : bb === undefined ? 1 : this._compare(aa, bb);
return direction === 'asc' ? res : -res;
});
}
protected abstract _matches(items: T, term: string) : boolean;
protected abstract _search(): Observable<SearchResult<T>>;
}
Client-Sortable Service: If the list manipulation happens in the client, derive the services from this one. I use a simple 1-minute cache to hold data.
import { HttpClient } from "#angular/common/http";
import { Observable, of } from "rxjs";
import { mergeMap } from "rxjs/operators";
import { SortableService } from "./sortable.service";
import { SearchResult } from "#app/common/_models";
export abstract class ClientSortableService<T> extends SortableService<T> {
cache?: T[];
cacheUpdatedIn?: Date;
constructor(protected _http: HttpClient, protected actionUrl: string) {
super(_http, actionUrl);
}
private getCache(): Observable<T[]> {
var expected = new Date();
if (this.cacheUpdatedIn !== undefined) {
expected = this.cacheUpdatedIn;
expected.setMinutes(expected.getMinutes() + 1);
}
//Search again.
if (this.cache == undefined || this.cache.length == 0 || expected == undefined || expected < new Date())
{
this.cacheUpdatedIn = new Date();
return this.getAll();
}
return of(this.cache || []);
}
protected _search(): Observable<SearchResult<T>> {
return this.getCache().pipe(mergeMap(items => {
this.cache = items;
//1: Sort.
let siteGroups = this._sort(items, this._state.sortColumn, this._state.sortDirection);
//2: Filter.
siteGroups = siteGroups.filter(group => this._matches(group, this._state.searchTerm));
const total = siteGroups.length;
//3: Paginate.
siteGroups = siteGroups.slice((this._state.page - 1) * this._state.pageSize, (this._state.page - 1) * this._state.pageSize + this._state.pageSize);
return of({ result: siteGroups, total: total });
}));
}
}
Server-Sortable Service: If the list manipulation happens in the server, derive the services from this one. The api need to handle the results before returning it.
import { HttpClient } from "#angular/common/http";
import { Observable, of } from "rxjs";
import { SortableService } from "./sortable.service";
import { SearchResult } from "#app/common/_models";
export abstract class ServerSortableService<T> extends SortableService<T> {
constructor(protected _http: HttpClient, protected actionUrl: string) {
super(_http, actionUrl);
}
protected _search(): Observable<SearchResult<T>> {
return super.post(this._state);
}
}
Custom Service: This custom service derives from the client-side base service. See that I need to declare a method that is used for the filtering, to select which fields to compare to.
#Injectable({ providedIn: 'root' })
export class ExampleService extends ClientSortableService<Example> {
constructor(protected _http: HttpClient) {
super(_http, `${environment.apiUrl}/sites`);
}
protected _matches(items: Example, term: string): boolean {
return items.displayName.toLowerCase().includes(term.toLowerCase());
}
}
So I beat my head against this for over a day.. I had it but I was doing something silly that broke pagination.
Anyway, what I did was all client-side since my data isn't going to be huge. In the constructor I wrapped the search.next() call in the subscribe of the API call. I also assigned the results to an array there. That way I had data when the search.next() was called. That array is what the actual function manipulates.
If you can follow my bad code here are the relevant snippets:
Declare a new variable to hold the results.
private myData: MyDataType[] = [];
In the constructor add your API call to get your data.
this.httpClient.get<MyDataType[]>('GetMyData').subscribe(data => {
this.myData = data;
this._search$.next();
});
This will load your data client-side to be used by the search function and trigger the initial call to said function.
And finally, in the search function, use your newly created and data-loaded variable for sorting and filtering.
//1. Sort
let theData = sort(this.myData, sortColumn, sortDirection);
I don't know what I was trying to do at first.. I went around the world and googled for several hours and found nothing that made it seem so simple. Just a few lines of code was all it took!

Enable Always on Top For Caliburn Managed Window

I have the following ViewModel and I am using Caliburn Micro. The IWindowManager instance is properly resolved and all of the code works. As indicated by the TODO comment, I need to get a reference to the current window so I can toggle the AlwaysOnTop attribute. How can I do that?
namespace CaliburnWizardPlay
{
[Export(typeof(DropWindowViewModel))]
public class DropWindowViewModel : PropertyChangedBase, IHaveDisplayName
{
private readonly IWindowManager windowManager;
[ImportingConstructor]
public DropWindowViewModel(IWindowManager windowManager)
{
this.windowManager = windowManager;
}
public string DisplayName
{
get { return "Main Window"; }
set { }
}
public bool AlwaysOnTop
{
get { return Settings.Default.DropWindowAlwaysOnTop; }
set
{
Settings.Default.DropWindowAlwaysOnTop = value;
Settings.Default.Save();
NotifyOfPropertyChange(() => AlwaysOnTop);
//todo: toggle the AOT attribute of the window
}
}
public void FileDropped(DragEventArgs eventArgs)
{
if (eventArgs.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] droppedFilePaths = eventArgs.Data.GetData(DataFormats.FileDrop, true) as string[];
foreach (string path in droppedFilePaths)
{
MessageBox.Show(path);
}
windowManager.ShowWindow(new WizardViewModel());
}
}
}
}
You can use the settings parameter of the ShowWindow method to set any property (e.g. Topmost) on the created window with a dictionary containing propertyname-value pairs:
windowManager.ShowWindow(new WizardViewModel(),
settings: new Dictionary<string,object> { {"Topmost", AlwaysOnTop} });
If you want to change the Topmost property of the already created window I see three options (in the order of preference):
Create an AlwaysOnTop property on the WizardViewModel and store the viewmodel in a private field and delegate the AlwaysOnTop to the WizardViewModel:
private WizardViewModel wizardViewModel;
public void FileDropped(DragEventArgs eventArgs)
{
//...
wizardViewModel = new WizardViewModel()
windowManager.ShowWindow(wizardViewModel);
}
public bool AlwaysOnTop
{
get { return Settings.Default.DropWindowAlwaysOnTop; }
set
{
//...
if (wizardViewModel != null)
wizardViewModel.AlwaysOnTop = value;
}
}
And in your view you can bind the WizardViewModel's AlwaysOnTop property to the window's TopMost property.
You can use the Application.Windows to retrieve the window. E.g. set the Name property of the created Window with the settings dictionary and then:
windowManager.ShowWindow(new WizardViewModel(),
settings: new Dictionary<string,object>
{ {"Topmost", AlwaysOnTop}, {"Name", "WizardWindow"} });
public bool AlwaysOnTop
{
get { return Settings.Default.DropWindowAlwaysOnTop; }
set
{
//...
var wizardViewModel = Application.Current.Windows.OfType<Window>()
.SingleOrDefault(w => w.Name == "WizardWindow");
if (wizardViewModel != null)
wizardViewModel.AlwaysOnTop = value;
}
}
Derive from the WindowManager and register it in your Bootstrapper and then you can override the CreateWindow, EnsureWindow etc. methods to store the created windows somewhere set the additional properties etc.

Scriptcontrol - bind client & server properties

Is it possible to bind properties on the client and server side in Scriptcontrol, so when I set property in javascript, change will be visible also in code behind and when I set property in code behind, change will be visible in javascript?
I can't get it work like above - it is set initially, when I set property where scriptcontrol is declared, but when I change it later it is still the same as before...
EDIT: I try to do a ProgressBar for long postbacks in our ASP.NET application. I have tried many options but none works for me... I want to set progress value in code behind and has it updated in view during long task postback.
Code for ScriptControl:
C#:
public class ProgressBar : ScriptControl
{
private const string ProgressBarType = "ProgressBarNamespace.ProgressBar";
public int Value { get; set; }
public int Maximum { get; set; }
protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
this.Value = 100;
this.Maximum = 90;
var descriptor = new ScriptControlDescriptor(ProgressBarType, this.ClientID);
descriptor.AddProperty("value", this.Value);
descriptor.AddProperty("maximum", this.Maximum);
yield return descriptor;
}
protected override IEnumerable<ScriptReference> GetScriptReferences()
{
yield return new ScriptReference("ProgressBar.cs.js");
}
}
Javascript:
Type.registerNamespace("ProgressBarNamespace");
ProgressBarNamespace.ProgressBar = function(element) {
ProgressBarNamespace.ProgressBar.initializeBase(this, [element]);
this._value = 0;
this._maximum = 100;
};
ProgressBarNamespace.ProgressBar.prototype = {
initialize: function () {
ProgressBarNamespace.ProgressBar.callBaseMethod(this, "initialize");
this._element.Value = this._value;
this._element.Maximum = this._maximum;
this._element.show = function () {
alert(this.Value);
};
},
dispose: function () {
ProgressBarNamespace.ProgressBar.callBaseMethod(this, "dispose");
},
get_value: function () {
return this._value;
},
set_value: function (value) {
if (this._value !== value) {
this._value = value;
this.raisePropertyChanged("value");
}
},
get_maximum: function () {
return this._maximum;
},
set_maximum: function (value) {
if (this._maximum !== value) {
this._maximum = value;
this.raisePropertyChanged("maximum");
}
}
};
ProgressBarNamespace.ProgressBar.registerClass("ProgressBarNamespace.ProgressBar", Sys.UI.Control);
if (typeof (Sys) !== "undefined") Sys.Application.notifyScriptLoaded();
I'll appreciate any way to implement this progress bar...
Personally, I do this often using hidden fields.
Bear in mind that hidden fields are not secure and may have other downfalls, since they don't actually hide their value, just simply do not display it.
ASPX Markup
<asp:HiddenField ID="hiddenRequest" runat="server" ClientIDMode="Static" />
ASPX.CS Code behind
public string HiddenRequest
{
set
{
hiddenRequest.Value = value;
}
get
{
return hiddenRequest.Value;
}
}
Page JAVASCRIPT (with jQuery)
$('#hiddenRequest').val('MyResult');
This way, I can access the same field using one variable as such, accessed both from client side and server side.

Resources