Angular 15, javascript loading on Safari

408 Views Asked by At

I'm learning Angular and there is something I dont really understand. My Application works fine on all browser (Chrome, Firefox, Brave, Opera, mobile version also) except Safari (desktop and mobile). My app launches great except for my script from the assets folder. ("src/assets").

angular.json

...
 
"scripts": ["src/assets/js/layout.js"]

...

layout.js

'use strict';

var layoutInit = function layoutInit(){
    const header = document.querySelector("header")// return null on safari;
    const main = document.querySelector("main") // return null on safari;
    const footer = document.querySelector("footer") // return null on safari;
    function resize(){
        main.style.minHeight = window.innerHeight - (footer?.offsetHeight ?? 0) + "px";
        main.style.paddingTop = (header?.offsetHeight ?? 0) + "px";
    }
     
    window.onresize = resize;
    resize();
}


if(document.readyState === 'loading' || document.readyState === 'interactive'){
    document.addEventListener('DOMContentLoaded', layoutInit)
}else{
    layoutInit()
}

Safari version : 15.6.1 Angular : 15

Does anyone experienced this issue ?

1

There are 1 best solutions below

4
On

First off: I can't answer the question. My guess is something to do with how/when Safari loads scripts?

However, I'd rather suggest alternatives as a better practice over what you currently have (unless you have it for a particularly good reason?); It is odd to have a lovely Angular SPA that can do everything you want, but then use an outside script to just set out the page layout.

  1. Move that into the app.

Assuming you have a basic app.component.html that looks suspiciously like this currently:

<div id="header">...</div>
<div id="main">
    <router-outlet></router-outlet>
</div>
<div id="footer>...</div>

Add in some view child usage:

<div id="header" #header>...</div>
<div id="main" #main>
    <router-outlet></router-outlet>
</div>
<div id="footer" #footer>...</div>

So they can be accessed in the app.component.ts:

@ViewChild('header') public header: ElementRef;
@ViewChild('main') public main: ElementRef;
@ViewChild('footer') public footer: ElementRef;

And then handle your resize in there too:

@HostListener('window:resize'. []) public onResize() {
    this.resizeLayout();
}

private resizeLayout(): void {
    if (!this.main) return; // make sure it all exists first

    this.main.style.minHeight = window.innerHeight - (this.footer?.offsetHeight ?? 0) + "px";
    this.main.style.paddingTop = (this.header?.offsetHeight ?? 0) + "px";
}

Something like that; might look a little different but that's a you problem not a me problem.

Probably want to put in either an ngOnInit or likely an ngAfterViewInit method to call that so it's all set up on first load (otherwise it'll ONLY be triggered on window resizing)...

public ngAfterViewInit(): void {
    this.resizeLayout();
}

That gets rid of your additional script and keeps it fully contained inside the app itself - triggered once when the app (view) is first initialised, and then subsequently after any window resizing is done.

That's a lot of effort for something that CSS can do, though.

  1. Use CSS - flex

My preference is to use Angular flex layout rather than raw CSS because 1) it's easier and 2) I personally like to keep base page structural details with the base page itself (the HTML).

Because of that, my memory for base CSS flex details is pretty poor, so I'll give it to you in AFL form in the template:

<div id="app-container" 
    fxFlexFill
    fxLayout="column"
    fxLayoutAlign="stretch stretch">

    <div id="header" fxFlex="100px">...</div>

    <div id="main" fxFlex>
        <router-outlet></router-outlet>
    </div>

    <div id="footer fxFlex="50px">...</div>
</div>

So long as you appropriately set up your html/body styling to flex layout, this will:

  • fxFlexFill: cause the app-container to fill the available space (e.g. if you had body { width: 100vw; height: 100vh; } then app-container now fills the whole of the window)
  • fxLayout="column": cause the child elements to be rendered down the page (row for across)
  • fxLayoutAlign="stretch stretch": cause the child elements to stretch up and down as well as across

Followed by the children:

  • fxFlex="100px": limit the header div to only 'stretch' to 100px
  • fxFlex: allow the main div to stretch as much as possible
  • fxFlex="50px": limit the footer div to only 'stretch' to 50px

There's some additional things needed to make it work a little nicer and work exactly how you want it to (e.g. #main { overflow: auto; }) but that gets you 90% of the way there with the layout you want and there's no actual scripting involved, just CSS.