I'm making a webapp with Angular 11 that uses the msgraph API to upload files to onedrive/sharepoint and then open the file in the Office online editor. This part is simple enough. I Also need to get the file back when the user is done with it. The requirement is that when the user closes the editor-tab I need to be notified of it, so I can download the file and delete it from onedrive/sharepoint.
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-test-page',
templateUrl: './test-page.component.html',
styleUrls: ['./test-page.component.scss']
})
export class TestPageComponent implements OnInit {
constructor() { }
winGoogle!: Window | null;
interval!: NodeJS.Timeout;
ngOnInit(): void {
this.interval = setInterval(this.detectClose, 1000);
}
openWin () {
this.winGoogle = window.open('http://google.com', '_blank');
}
closeWin () {
if(this.winGoogle) {
this.winGoogle.close();
}
}
detectClose() {
//detect if the tab is closed by the user ( not from code ) and remove the interval
clearInterval(this.interval)
}
}
I tried to look at the value of the "handler" (this.winGoogle), but its undefined at all times.
Does anyone know how can I achieve it or is it at all possible?
Seems like it can't be done without writing a browser extension.
Related
I'm implementing security for the applications at the company I'm working at right now. I'm using #azure/msal-angular#2.0.2, #azure/msal-browser#2.16.1. I followed the example found here
and got it working for the first application. I went on to implement it for the next application, which is basically the same one, just talks to a different api, but the complexity is the same. After possibly doing something wrong I keep getting the error:
core.js:6157 ERROR Error: Uncaught (in promise): BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors.
BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors.
I found several other posts that said to clear the caches, storages etc... But none of it works. All it does is prompt me to log in again, only to fill in the sessionStorage back with the same entries, amongst them the entry that says msal.442edcf7-9e0c-469b-93fb-daa1839724bd.interaction.status interaction_in_progress. As far as I know I've tried everthing I've found. Also the solution from AzureAD themselves doesn't work.
I'm very new to this so I might have missed something simple, if so, my apologies.
My code can be found below.
Angular version
11.0.2
app.module.ts
import {
MsalBroadcastService,
MsalGuard,
MsalInterceptor,
MsalModule, MsalRedirectComponent,
MsalService
} from "#azure/msal-angular";
import {BrowserCacheLocation, InteractionType, LogLevel, PublicClientApplication} from '#azure/msal-browser';
const isIE = window.navigator.userAgent.indexOf('MSIE ') > -1 || window.navigator.userAgent.indexOf('Trident/') > -1;
#NgModule({
declarations: [
AppComponent,
HeaderComponent,
RibonComponent
],
imports: [
BrowserModule,
AppRoutingModule,
...,
MsalModule.forRoot(
new PublicClientApplication({ // MSAL Configuration
auth: {
clientId: "442edcf7-...",
authority: "https://login.microsoftonline.com/81fa766e-...",
redirectUri: window.location.origin,
},
cache: {
cacheLocation : BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: false, // set to true for IE 11
},
system: {
loggerOptions: {
loggerCallback: (level: LogLevel, message: string, containsPii: boolean): void => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
}
},
piiLoggingEnabled: false
}
}
}), {
interactionType: InteractionType.Popup, // MSAL Guard Configuration
}, {
protectedResourceMap: new Map([
[ 'http://localhost:8400/', ['api://442edcf7-9e0c-469b-93fb-daa1839724bd/acces_as_user/Acces-user']],
[ 'https://quality-score-dev-pcq-dev.bravo-apps.volvocars.biz/', ['api://442edcf7-9e0c-469b-93fb-daa1839724bd/acces_as_user/Acces-user']]
]),
interactionType: InteractionType.Redirect // MSAL Interceptor Configuration
}
)
],
providers: [
EnvServiceProvider,
{
provide: MatPaginatorIntl,
useClass: MatPaginatorIntlTranslator
},
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
MsalService,
MsalGuard,
MsalBroadcastService,
],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy{
title = 'Quality score';
isIframe = false;
loginDisplay = false;
activeUser = '';
private readonly onDestroy$ = new Subject<void>()
constructor(
#Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
private languageService: LanguageService,
private authService: MsalService,
private msalBroadcastService: MsalBroadcastService,
private location: Location,
private userService: UserService
){}
ngOnInit(): void {
const currentPath = this.location.path();
// Dont perform nav if in iframe or popup, other than for front-channel logout
this.isIframe = BrowserUtils.isInIframe() && !window.opener && currentPath.indexOf("logout") < 0; // Remove this line to use Angular Universal
this.msalBroadcastService.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
takeUntil(this.onDestroy$)
)
.subscribe(() => {
this.setLoginDisplay();
this.checkAndSetActiveAccount();
})
}
ngOnDestroy() {
this.onDestroy$.next();
}
setLoginDisplay() {
this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
}
checkAndSetActiveAccount(){
/**
* If no active account set but there are accounts signed in, sets first account to active account
* To use active account set here, subscribe to inProgress$ first in your component
* Note: Basic usage demonstrated. Your app may require more complicated account selection logic
*/
let activeAccount = this.authService.instance.getActiveAccount();
if (!activeAccount && this.authService.instance.getAllAccounts().length > 0) {
let accounts = this.authService.instance.getAllAccounts();
this.authService.instance.setActiveAccount(accounts[0]);
}
if(activeAccount) {
this.userService.activeUserName = activeAccount.name;
this.activeUser = activeAccount.name;
}
}
logout(): void{
this.authService.logoutRedirect();
}
}
I'm really lost and have no idea what I have done wrong. From my understanding there is a login process that was interupted, probably by me leaving the login screen, but now I have no idea how to "finish it up" or complete the process.
Update
I tried copying the LocalStorage values from the working application and I got it to work. Refreshing works and no errors appear, but when I logout and after it prompted me to login again and I do, then it's right back to the start again.
Solution update
I've had a breakthrough. If I change the login type to Popup and handle it this way, it's fixed. I can login and logout without any issues. However if I then change it back to Redirect, it's broken again. So for now I'll keep it on Popup. Simple solution, but I hadn't thought of it because I assumed the issue would be occur there as well.
I can't tell by the code provided, but I had a similar issue. On top of that I was getting the following error in the console: Error: The selector "app-redirect" did not match any elements
This lead me to the following post: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/3114#issuecomment-788394239
I added <app-redirect></app-redirect> to the index.html page, below app-root as suggested and this seemed to sort both issues.
"Workaround" fix
Make your login type Popup. Dumb of me not to think about that
I am developing a page that shows real-time data from a server. Now i'm testing it with some mqtt client websocket (like hivemq). The value itself that i receive is showed in the chrome console,but i'm trying to make this value graphical with NGX-GAUGE.
The ngx-gauge is showed correctly in the page,and if i put in "gaugeValue" a standard number it works (also with a Math.Random), but if i take a value from a MQTT broker,it just doesn't do anything
when i try to get the value from an MQTT broker, the value and green line of the ngx-gauge (which should increase/decrease in real time) doesn't do anything
import { Component, OnInit } from '#angular/core';
import { Paho } from 'ng2-mqtt/mqttws31';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
valore:String;
gaugeType = "semi";
gaugeValue=this.valore;
gaugeLabel = "Valore";
gaugeAppendText = "km/hr";s
animate=true;
duration=1500;
private client;
mqttbroker = 'broker.mqttdashboard.com';
ngOnInit() {
this.client = new Paho.MQTT.Client(this.mqttbroker, Number(8000), 'client1');
this.client.onMessageArrived=this.onMessageArrived.bind(this);
this.client.onConnectionLost=this.onConnectionLost.bind(this);
this.client.connect({onSuccess: this.onConnect.bind(this)});
}
onConnect() {
console.log('onConnect');
this.client.subscribe('testtopic/40/xxx');
}
onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log('onConnectionLost:' + responseObject.errorMessage);
}
}
onMessageArrived(message) {
console.log('onMessageArrived: ' + message.destinationName + ': ' + message.payloadString);
if (message.destinationName.indexOf('xxx') !== -1) {
this.valore = (message.payloadString);
}
}
}
It should simply show the value,with the line respondig in real time with that value
I also faced the same issue but I solved it after writing code by this way :
gaugeValue= 0; //define minimum value in export
this.gaugeValue= (message.payloadString); // intead of "this.valore" use this.gaugeValue
Please refer screenshot 1 & 2 for more understandings;
I used _thoughput_gaugeValue for defining
same _thoughput_gaugeValue is used as this._thoughput_gaugeValue for getting data. Do not declare _thoughput_gaugeValue:any
I was developing an Angular app, in my dashboard.comp.ts I need to implement jquery event (due to other reason I have used jquery), jquery event working properly with my typescript objects. Now problem is when I click through jquery, DOM manipulation getting delay, so that, output getting delay like: 5/7 seconds.
dashboard.comp.ts
import { Component, OnInit} from '#angular/core';
import SensorServices from '../Services/Settings/SensorServices';
declare var $: any;
#Component({
selector: 'app-root',
templateUrl: '../Views/Dashboard.html',
providers: [SensorServices]
})
export class DashboardComponent implements OnInit {
public sensor: any;
constructor(private _sensorServices: SensorServices )
{
}
ngOnInit(): void {
this._sensorServices.getAll({})
.subscribe((result) => {
var self=this;
$(document).on('click', '.sensorSizeInDesign', function (e) {
self.sensor=result;
});
});
}
}
html
N.B: the given html is fewer basically element generated from server side, so there are multiple sensors. here is an example.
<div class='sensorSizeInDesign' data-sensorId="123"></div>
<h3>{{sensor.Port}}</h3>
For each click event Port getting delay to be changed.
avoid using jQuery inside angular applications. Therefor #ViewChild exists:
https://angular.io/api/core/ViewChild
Your click handler should be implemented this way:
function onClick() {
this._sensorServices.getAll({}).subscribe((result) => {
this.sensor=result;
})
}
HTML:
<div class='sensorSizeInDesign' (click)="onClick()" data-sensorId="123"></div>
<h3>{{sensor.Port}}</h3>
ngOnInit(): void {
this._sensorServices.getAll({})
.subscribe((result) => {
var self=this;
$( window ).on( "load", function(){
$(document).on('click', '.sensorSizeInDesign', function (e) {
self.sensor=result;
});
});
});
Try that
Finally i got the solution: just updating my jquery code:
ngOnInit(): void {
this._sensorServices.getAll({})
.subscribe((result) => {
var self=this;
$('.sensorSizeInDesign').click(function(e){
self.sensor=result;
})
});
}
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 have 2 pages Page1 and Page2. I have used this.nav.pop() in Page2 and it will pop the Page2 and Page1 will enable but i want to refresh the Page1.
Thank you in advance.
you could pass the parent page along with the nav push. that way you could accces the parent page as a navParamter.
in parent page:
goToChildPage() {
this.navCtrl.push(ChildPage, { "parentPage": this });
}
and in the child page before pop you could call functions on parent page
this.navParams.get("parentPage").someFnToUpdateParent();
//or
this.navParams.get("parentPage").someFnToRefreshParent();
Ignore the direct angular implementations suggested here, especially since you are using Ionic 2 and the suggestions are assuming Ionic 1. Don't start mixing too much of direct angular in your ionic app unless there is no ionic implementation for what you need. Import "Events" from ionic/angular2 in both Page1 and Page2, then in Page2 do something like
this.events.publish('reloadPage1');
this.nav.pop();
And in Page1 put
this.events.subscribe('reloadPage1',() => {
this.nav.pop();
this.nav.push(Page1);
});
You may want to implement one of these in your page:
ionViewWillEnter
ionViewDidEnter
Please review the navController and page lifecycle documentation:
http://ionicframework.com/docs/v2/api/components/nav/NavController/
Simple solution that worked for me was calling the get service method again in ionViewDidEnter
ionViewDidEnter() {
this.loadGetService();
}
On PAGE 1:
import { Events } from 'ionic-angular'
constructor(public events:Events){
this.listenEvents();
}
... ...
listenEvents(){
this.events.subscribe('reloadDetails',() => {
//call methods to refresh content
});
}
On PAGE 2:
import { Events } from 'ionic-angular'
constructor(public events:Events, public navCtrl:NavController){
}
function(){
this.events.publish('reloadDetails');
this.navCtrl.pop();
}
You may consider send an event before call this.nav.pop to let page 1 reload itself.
Like Jonathan said, you can import Events from ionic-angular, but you don't need push and pop again, call your methods to reload only the content.
In page2:
this.events.publish('reloadDetails');
this.navCtrl.pop();
In page1:
this.events.subscribe('reloadDetails',() => {
//call methods to refresh content
});
That works for me.
I simply load the details in page 1 in an ionViewWillEnter function (using Ionic 2). This handles both the initial load and any refresh when popping back to page 1.
Documentation is here.
ionViewWillEnter
"Runs when the page is about to enter and become the active page."
I found this technique to reload a page:
this.navCtrl.insert(1, MyPage);
this.navCtrl.pop();
I had the same problem and spend many hours searching and trying the solution.
If I understand, your problem is:
Page 1 have some bindings that you get from an API / Webservice.
Page 2 have some inputs and when pushing the back button (pop) you want to SAVE data + refresh the Page 1 bindings.
The way I solved it has been reading a post on StackOverflow that now I can't find :( !!
The solution is using an Injectable Service.
PAGE 1:
/* IMPORTS */
import { App, Nav, NavParams } from 'ionic-angular';
import { Oportunidades } from '../../services/oportunidades.service';
/* SOME BINDINGS HERE */
{{oportunidades.mesActual.num_testdrive}}
/* CONSTRUCTOR */
constructor(
private oportunidades: Oportunidades, // my injectable service!
public app: App,
public nav: Nav,
public params: NavParams
) {
// Call to the Injectable Service (named oportunidades):
this.oportunidades.getOportunidades();
}
INJECTABLE SERVICE:
/* IMPORTS */
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class Oportunidades {
public url = 'http://your-API.URL';
public data: Observable<Object>;
public mesActual: Object = [];
constructor(private http: Http){
//GET API DATA
this.data = http.get(this.url).map(res => res.json());
}
getOportunidades() {
this.data.subscribe(data => {
this.mesActual = new MesActual(
data["mes_actual_slide"].num_testdrive,
...
//Here I get the API data and set it on my injectable object
);
});
}
}
PAGE 2:
/* SOME IMPORTS */
import { NavController } from 'ionic-angular';
import { UserData } from '../../services/data.service';
import { Oportunidades } from '../../services/oportunidades.service';
import { Http, Headers, URLSearchParams } from '#angular/http';
/* SOME example BINDINGS and INPUTS: */
#Component({
template: `
{{ day[selectedDay].dia }}
Input data:
<ion-input type="number" clearOnEdit="true"
#ventas id="ventas" value={{day[selectedDay].ventas}}
(keyup)="setVal(ventas.value, $event)">
</ion-input>
`,
providers: [
]
})
export class PageInsert {
constructor(
public navCtrl: NavController,
private http: Http,
private userData: UserData,
public oportunidades: Oportunidades // my injectable service!
) {
send(selectedDay){
var url = 'http://your.api.url/senddata';
// I SAVE data TO API / Webservice
this.http.post(url, params, { headers: headers })
.map(res => res.json())
.subscribe(
data => {
console.log(data);
// Here i'll call to the Injectable service so It refresh's the new data on Page1
// in my case, this send function is called when "pop" or "back" button of ionic2 is pressed
// This means: On click on back button -> Save and refresh data of the Injectable that is binded with the Page1
this.oportunidades.getOportunidades();
return true; },
error => {
console.error("Error saving!");
}
);
}
}
I hope it can help you!! Ask for any similar problems :)
I spent a day and a half on a similar issue. The solution is anti-climatic really.
I'm passing a FormGroup from Page-1 to Page-2. I update the FormGroup values in Page-2. When I pop Page-2, Page-1's form is not updated with the new values. It hasn't been watching for changes.
To fix this, I patch the FormGroup with itself after Page-2 has been popped but still in the Page-2 component.
This is more responsive, but requires a direct call to close().
// Page-2 close method
public close(): void {
this.navCtrl.pop();
this.formGroup.patchValue(this.formGroup.value);
}
This is all encompassing, but I do see the refresh on Page-1.
// Page-2 nav controller page event method
ionViewWillUnload() {
this.formGroup.patchValue(this.formGroup.value);
}
In some situations instead of pop() you can use the push() function. When you enter the page with the push() function it is reloaded.
Also you can remove page2 from the navigation.
this.navCtrl.push(TabsPage).then(() => {
const index = this.viewCtrl.index;
this.navCtrl.remove(index);
});
Or if you have more than one page for example page1->page2->pag3:
this.navCtrl.push(TabsPage).then(() => {
const index = this.viewCtrl.index;
this.navCtrl.remove(index, 2); //this will remove page3 and page2
});
ionViewWillEnter() {
this.refresh();
}
ionViewWillEnter will be called just before any time you (re)visualize the page
Please checkout my solution, which I've posted here:
https://forum.ionicframework.com/t/ionviewdidenter-is-not-invoked-on-leaving-a-pushed-page/131144/19?u=unkn0wn0x
Maybe you can adapt it to your needs.
The main point of my intention was to prevent, passing a whole module with this as a navCtrlParam into the push()'ed page, like it was mentioned in some comments before.
Hope it helps!
Cheers
Unkn0wn0x