IE not calling lifecycle hooks or filling #Input until change detected? - internet-explorer-11

I'm using beta.0 because this outstanding bug prevents angular 2 from working in IE in beta.1 and beta.2.
Relevant code from SearchBar.ts
#Component({
selector : 'search-bar',
templateUrl: 'views/searchbar.html'
})
export class SearchBar {
private history: SearchHistoryEntry[] = [];
#Output() onHistory = new EventEmitter();
constructor() {
this.history = JSON.parse(localStorage.getItem('SearchHistory')) || [];
}
ngOnInit() {
// The constructor doesn't have #Outputs initialized yet. Emit from this
// life cycle hook instead to be sure they're received on the other side
debugger;
this.onHistory.emit(this.history);
}
}
Relevant code from home.html
<search-bar (onHistory)="SearchBarHistory($event)"></search-bar>
Relevant code from home.ts
SearchBarHistory(history: SearchHistoryEntry[]) {
debugger;
this.history = history;
}
In Chrome this works just fine. The SearchBar's constructor correctly reads from localStorage, in ngOnInit it emits to my Home component who receives it, it's stored locally and the UI bindings tied to history update to show the information as it all should.
In IE 11 this does not work. ngOnInit won't run until I click inside my search bar. It seems that any #Input or lifecycle hook (specifically I've tested ngOnInit, ngAfterContentInit, and ngAfterViewInit and they all behave the same) doesn't run until the component's change detection is triggered. If I refresh the page then it runs exactly like Chrome where no interaction is required for #Inputs or lifecycle hooks to be called and my history goes through and gets bound like it should.
I think this is a bug of the beta but in the mean time is there anything I can do to make it work the first time without an interaction or page refresh?

I am having the same issue I tried it to resolve by forcing detectChanges like:
import {Injectable,ApplicationRef, NgZone} from '#angular/core';
#Injectable()
export class IeHackService {
constructor(
private _appRef: ApplicationRef,
private _zone: NgZone) {}
private isIe() {
let ua = window.navigator.userAgent;
let msie = ua.indexOf('MSIE ');
if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) // If Internet Explorer, return version number
return true;
return false;
}
onComponentLoadInIe() {
if (this.isIe()) {
this._zone.run(() => setTimeout(() => this._appRef.tick(), 5));
}
}
}
Then in Every Route component that uses Lifecycle Hooks I called
constructor(private dialogService: ModalDialogService,ieHackService: IeHackService) {
ieHackService.onComponentLoadInIe();
}

I had this issue as well, I used a workaround to automatically refresh the page if the bug occurs, hoping that the bug will eventually be solved.
It's very ugly, but for now it works at least.
declare var Modernizr: any;
#Component({
selector : 'search-bar',
templateUrl: 'views/searchbar.html'
})
export class SearchBar {
private history: SearchHistoryEntry[] = [];
#Output() onHistory = new EventEmitter();
ie11hack: boolean = true;
constructor() {
this.history = JSON.parse(localStorage.getItem('SearchHistory')) || [];
if (!Modernizr.es6collections || navigator.userAgent.toLowerCase().indexOf("safari") !== -1) {
setTimeout(() => {
if (this.ie11hack) {
window.location.reload();
}
}, 500);
}
}
ngOnInit() {
ie11hack = false;
// The constructor doesn't have #Outputs initialized yet. Emit from this
// life cycle hook instead to be sure they're received on the other side
debugger;
this.onHistory.emit(this.history);
}
}
Edit, a less ugly partial fix:
The issues is (I think) caused by using a direct url rather then using the angular router (javascript).
If you want your website to work if people enter their url manually then you still need the above hack, but otherwise you may do what I did below (you may want to do that anyway).
I changed this:
<!-- html -->
<a href="#/objects/objectthingy/{{myObject.id}}" class="my-object-class">
To this:
<!-- html -->
<a href="javascript:void(0)" (click)="openMyObject(myObject.id)" class="my-object-class">
// typescript
openMyObject(objectId: number) {
this.router.navigate(['/Objects', 'ObjectThingy', { id: objectId}]);
}
and ngAfterViewInit method was called again.

Related

Vue.js component not loading/rendering data when called via URL or F5

I have a Vue.js SPA with some pages that display data from a backend. When I navigate the pages via the navbar, everything works fine, components and data are loaded.
When I'm on the page, e.g. localhost:8080/#/mypage and press F5, the data doesn't get loaded / rendered. Same goes for when I directly navigate to the page via the address bar.
The data gets loaded in this function:
async beforeMount() {
await this.initializeData();
}
I've tried to call the method in every lifecycle hook, i.e. created, beforeCreated, mounted etc...
In the mounted lifecycle hook I'm setting a boolean property to true, so that the table is only rendered when the component is loaded (done with v-if).
mounted() {
this.componentLoaded = true;
}
Not sure if this is important, but I've tried it with or without and it doesn't work.
I would really appreciate it if somebody knew whats happening here.
EDIT:
this.applications is a prop and contains multiple applications which contain instances. I want to add some variables from the backend to each application.
console.log(1) gets printed
console.log(2) does not
initializeData: function () {
let warn = 0;
console.log("1");
this.applications.forEach(async application => {
const instance = application.instances[0];
console.log("2");
let myData = null;
try {
const response = await instance.axios.get('url/myData');
myData = response.data;
} catch (err) {
}
let tmpCount = 0;
let tmpFulfilled = 0;
myData.forEach(ba => {
if(!ba.fulfilled){
warn++;
application.baAllFulfilled = false;
}else {
tmpFulfilled++;
}
tmpCount++;
})
console.log("3");
// Assign values
this.baTotalWarnings = warn;
application.baAnzahl = tmpCount;
application.baFulfilled = tmpFulfilled;
this.componentLoaded = true;
}
Try removing the async and await keywords from your beforeMount, and remove this.componentLoaded from mounted. Set it instead in the then block (or after await) in your initializeData method. I'm not sure Vue supports the async keyword in its lifecycle methods.
Something like this:
beforeMount() {
this.initializeData(); // start processing the method
}
methods: {
initializeData() {
callToBackend().then(() => {
this.componentLoaded = true // backend call ready, can now show the table
})
}
}

Angular 5: Route animations for navigating to the same route, but different parameters

In my Angular 5 application, the user may navigate to a route which uses the same route, but with different parameters. For example, they may navigate from /page/1 to /page/2.
I want this navigation to trigger the routing animation, but it doesn't. How can I cause a router animation to happen between these two routes?
(I already understand that unlike most route changes, this navigation does not destroy and create a new PageComponent. It doesn't matter to me whether or not the solution changes this behavior.)
Here's a minimal app that reproduces my issue.
This is an old question but that's it if you're still searching.
Add this code to your app.Component.ts file.
import { Router, NavigationEnd } from '#angular/router';
constructor(private _Router: Router) { }
ngOnInit() {
this._Router.routeReuseStrategy.shouldReuseRoute = function(){
return false;
};
this._Router.events.subscribe((evt) => {
if (evt instanceof NavigationEnd) {
this._Router.navigated = false;
window.scrollTo(0, 0);
}
});
}
By using this code the page is going to refresh if you clicked on the same route no matter what is the parameter you added to the route.
I hope that helps.
Update
As angular 6 is released with core updates you don't need this punch of code anymore just add the following parameter to your routs import.
onSameUrlNavigation: 'reload'
This option value set to 'ignore' by default.
Example
#NgModule({
imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload'})],
exports: [RouterModule]
})
Stay up to date and happy coding.
I ended up creating a custom RouteReuseStrategy which got the job done. It's heavily based on this answer.
export class CustomReuseStrategy implements RouteReuseStrategy {
storedRouteHandles = new Map<string, DetachedRouteHandle>();
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return false;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
this.storedRouteHandles.set(route.routeConfig.path, handle);
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
return false;
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
return this.storedRouteHandles.get(route.routeConfig.path);
}
// This is the important part! We reuse the route if
// the route *and its params* are the same.
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig &&
future.params.page === curr.params.page;
}
}
Check it out on StackBlitz!

Auth2 Object unidentified when trying to sign out (Angular2)

Good Day,
I am trying to sign out an auth2 client. This process was working fine before I upgraded my router to fit in with new RC requirements. Now it seems as if the auth2 object is cleared or lost along the way from signing in to signing out.
Here is my sign out tag:
<a role="button" (click)="signOut()" style="padding-left: 30px;">Log out</a>
it simply calls a signOut() function found in navbar.component.ts (See below)
signOut() {
var auth2 = this._navigationService.getAuth2();
auth2.signOut().then(function () {
});
console.log('User signed out.');
sessionStorage.clear();
localStorage.clear();
this.router.navigate(['Login'])
window.location.reload()
}
here is the navigationService code it is calling:
import { Injectable } from '#angular/core';
#Injectable()
export class NavigationService {
onEditMode:boolean;
auth2:any;
constructor() {
this.onEditMode=true;
}
getEditMode(){
return this.onEditMode;
}
setEditMode(editMode:boolean){
this.onEditMode=editMode;
}
setAuth2(auth2:any){
this.auth2=auth2;
}
getAuth2(){
return this.auth2;
}
}
Here is my login.component.ts which sets the auth2 object seen in navigationService.ts:
onGoogleLoginSuccess = (loggedInUser) => {
this.isLoading=true;
console.log(loggedInUser)
this._navigationService.setAuth2(gapi.auth2.getAuthInstance());
console.log("Google gapi" + gapi.auth2.getAuthInstance());
sessionStorage.setItem('gapi',gapi.auth2.getAuthInstance());
this._zone.run(() => {
this.userAuthToken = loggedInUser.hg.access_token;
this.userDisplayName = loggedInUser.getBasicProfile().getName();
var strClientID = document.getElementsByTagName('meta')['google-signin-client_id'].getAttribute('content')
this.objTrimbleAuthentication.ClientID = document.getElementsByTagName('meta')['google-signin-client_id'].getAttribute('content');
this.objTrimbleAuthentication.IDToken = loggedInUser.getAuthResponse().id_token;
this._trimbleAuthenticationService.sendAndVerify(this.objTrimbleAuthentication).subscribe(data=>{
if(data.tokenIsValid==true){
sessionStorage.setItem('S_USER_EMAIL',loggedInUser.getBasicProfile().getEmail());
sessionStorage.setItem('S_USER_NAME',loggedInUser.getBasicProfile().getName());
sessionStorage.setItem('S_ID_TOKEN',this.userAuthToken);
this.objExternalBindingModel.ExternalAccessToken=this.userAuthToken;
this.objExternalBindingModel.Provider="Google";
this.objExternalBindingModel.UserName = loggedInUser.getBasicProfile().getName();
this._LoginService.obtainLocalAccessToken(this.objExternalBindingModel).subscribe(data=>{
// console.log(data);
this.isLoading=false;
this._router.navigate(['/Home']);
sessionStorage.setItem("access_token",data.access_token);
},error=>{
console.log(error);
})
}else{
this.isLoading= false;
this.showModal('#trimbleAuthError');
}
}, error=>{
})
});
}
onGoogleLoginSuccess is called from login.component.html:
<div style="margin-left:8% !important" id="{{googleLoginButtonId}}"></div>
So this process was working fine until I update my router to use the latest Angular2 Release Candidate. I am out of ideas on what could possibly be causing the following error when I click the sign out button:
Error in component.html/navbar.component.html:12:33
ORIGINAL EXCEPTION: TypeError: Cannot read property 'signOut' of undefined
if you need any other information or components please ask I hope I have given enough information. As I said it was working so keep that in mind, please.
Update
Waiting for additional info ...
In the following code, auth2:any; is undeclared. Is setAuth2 called anywhere before signOut()?
import { Injectable } from '#angular/core';
#Injectable()
export class NavigationService {
onEditMode:boolean;
auth2:any;
constructor() {
this.onEditMode=true;
}
getEditMode(){
return this.onEditMode;
}
setEditMode(editMode:boolean){
this.onEditMode=editMode;
}
setAuth2(auth2:any){
this.auth2=auth2;
}
getAuth2(){
return this.auth2;
}
}
Base on limited information and code posted, my guess is a logical bug in the logout process.
In signOut(), the window.location.reload() reload the page at the current url, which also clear all variables/objects. However, after reload, your app properly try to do signout again (due to url?).
In your navbar.component, you may need to add more logic in ngInit() to handle the situation.
Or can your code work without window.location.reload()? It seems odd to use that with angular2, especially with routing.
Right, the solution i found to the above question was that signing out using localhost will not work. So i just used this block of code when deploying the website and keep it commented out when running the website on localhost.
this is my signOut() function found in navbar.component.ts:
signOut() {
//////////////////////////////////////// Uncomment block for live deployment //////////////////////////////
// var auth2 = gapi.auth2.getAuthInstance();
// auth2.signOut().then(function () {
// console.log('User signed out.');
// });
//////////////////////////////////////////////////////////////////////////////////////////////////////////
sessionStorage.clear();
localStorage.clear();
this.router.navigate(['/']);
window.location.reload();
}
although getAuthInstance gives an error when trying to run it in localhost, deploying the web application to a server seems to work fine.

Aurelia event won't fire on first page load

I'm using Aurelia's EventAggregator to publish and subscribe to events in my app. Some of my custom elements take a while to load, so I've used a loading event to tell my main app.js to add a spinner to the page during loading.
This works fine once the app has loaded and I start switching between routes, however, on first page load the event doesn't seem to fire - or at least, it isn't picked up by the subscribe method.
Here's basically what my app.js does:
attached () {
this.mainLoadingSubscription = this.eventAggregator.subscribe('main:loading', isLoading => {
// If main is loading
if (isLoading) {
document.documentElement.classList.add('main-loading');
}
// Stopped loading
else {
document.documentElement.classList.remove('main-loading');
}
});
}
And here's what my custom elements do:
constructor () {
this.eventAggregator.publish('main:loading', true);
}
attached () {
this.doSomeAsyncAction.then(() => {
this.eventAggregator.publish('main:loading', false);
});
}
This causes the first page load to not show a spinner and instead the page looks kind of broken.
Btw, I am aware of the fact that you can return a Promise from the element's attached method but I can't do this because of this other problem
Set up your subscriptions in your viewModel's constructor or activate callback
In the above example, you set up subscriptions in the viewModel's attached() callback. Unfortunately, this will not be called until all child custom element's attached() callbacks are called, which is long after any one custom element's constructor() function is called.
Try this:
app.js
#inject(EventAggregator)
export class AppViewModel {
constructor(eventAggregator) {
this.mainLoadingSubscription = eventAggregator.subscribe('main:loading', isLoading => {
// do your thing
}
}
}
If the viewModel is a route that can be navigated to, then handle this in the activate() callback with appropriate teardown in the deactivate() callback.
#inject(EventAggregator)
export class AppViewModel {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
}
activate() {
this.mainLoadingSubscription = this.eventAggregator.subscribe('main:loading', isLoading => {
// do your thing
}
}
deactivate() {
this.mainLoadingSubscription.dispose();
}
}

Angular 2 - Use ComponentResolver to load component downloaded at runtime

I'm trying to load a component dynamically in my Angular2 app.
By dynamically I don't just mean to insert into the DOM a component that is missing from the parent component's template; I mean to download from the server another component, because the User might not ever need it, and then insert it into the DOM.
So I followed this to understand how to load the component with Systemjs and now I see that, in RC4, we need to use this and ComponentResolver to inject things into the DOM.
So here is my code for the module-wrapper component that should put things together:
export class ModuleWrapperComponent implements OnInit {
#ViewChild("module", { read: ViewContainerRef }) target: ViewContainerRef;
#Input() isOpen: boolean;
#Input() moduleId: number;
type: string = null;
moduleRef: ComponentRef<any> = null;
private isViewInitialized: boolean = false;
constructor(private resolver: ComponentResolver) { }
private _loadModule(moduleId: number): Promise<string> {
if (!!this.type)
return Promise.resolve(this.type);
var mod = {
component: "DashboardComponent",
path: "/app/modules/dashboard.component"
};
return System
.import(mod.path)
.then(() => this.type = mod.component);
}
updateComponent() {
if (!this.isViewInitialized)
return;
if (!!this.moduleRef)
this.moduleRef.destroy();
if (this.isOpen) {
this._loadModule(this.moduleId)
.then(type => {
this.resolver.resolveComponent(type)
.then(factory => {
this.moduleRef = this.target.createComponent(factory)
// to access the created instance use
// this.compRef.instance.someProperty = 'someValue';
// this.compRef.instance.someOutput.subscribe(val => doSomething());
});
})
}
}
ngOnChanges() {
this.updateComponent();
}
ngAfterViewInit() {
this.isViewInitialized = true;
this.updateComponent();
}
ngOnDestroy() {
if (!!this.moduleRef)
this.moduleRef.destroy();
}
}
Sadly, I get this error from ComponentResolver:
Error: Cannot resolve component using 'DashboardComponent'. at new BaseException$1 (http://localhost:63815/lib/#angular/compiler//bundles/compiler.umd.js:971:27)
I reckon that loading the module in Systemjs is not enough...so how do I tell ComponentResolver that I've downloaded a new component? The official documentation is still young and lacking...
On a side note, say that my Dashboard component loads additional stuff with imports...Systemjs will handle all that automatically, won't it?
Thanks in advance!
This does not exactly answer your question.
BUT,
We had a similar requirement and were loading components dynamically using SystemJs.
It was all working fine until we started thinking about build and deployment strategies and came across Webpack
Since the value of moduleId is only known at runtime, so webpack will not be able to resolve the actual module components at compile time and will not bundle those files.
You will need to have static imports for your dynamic module components somewhere in your code if you want them to be bundled as part of your single app.js