hawzie197 / flask-angular-skeleton

A boiler plate for building apps using server to client side rendering with Angular 4 and Flask.
MIT License
56 stars 24 forks source link

Angular Refresh issue with ng build #5

Open mmatlock-shr opened 6 years ago

mmatlock-shr commented 6 years ago

When using this skeleton, if you add routes to the Angular project (or if you swap the angular project in this one for your angular5 angular material project as you mention), and then when in a route refresh the page, you get a page not found error.

I'm having this in all flask angular4/5 projects that I'm working in, so I know it's not necessarily an issue with this particular project, I'm mostly curious if you've found a way around this problem. I suspect it's related to (or the exact same thing as) the normal Single Page Application problem where everything needs to be redirected to index.html... but I thought this project already did that, and I have things set up in my other projects to do essentially the same, or so I thought...

In some of our projects, we are using Flasgger for api documentation, so that is one difference, but the same behavior is seen with this Skeleton too. The refresh works if using ng serve, but not ng build...

I've tried the following in mine...:

Swagger(app)

app.template_folder = "./templates/dist/"
app.static_folder = "./templates/dist/"
app.static_path = "./templates/dist/"
app.static_url_path = "./templates/dist/"
app.instance_path = "./templates/dist/"

config = settings.config()
config.read("settings.cfg")

@app.route('/', defaults={'path': ''}, methods=['GET', 'POST'])
@app.route('/<path:path>', methods=['GET', 'POST'])
def get_resource(path):  # pragma: no cover
    print(path)
    print(config.getKey("devmode"))
    # If we are devmode, proxy through flask.
    if config.getKey("devmode") == True:
        print('inside devmode')
        url = "http://localhost:%s/%s" % (config.getKey('ng_port'), path)
        j = requests.get(url, stream=True, params=request.args)
        return make_response(j.text)
    # return the results
    # return send_static_file("index.html")
    return send_from_directory('templates/dist/', path)

if __name__ == '__main__':
    app.run(debug=True, port=int(config.getKey("port")))
hawzie197 commented 6 years ago

Hi @mmatlock-shr,

I had this problem on my angular-django application for a company platform I am working on. In order to get the refresh to work correctly, you need to add in # hash routing. In the app.module.ts file, inside your imports: [], add in RouterModule.forRoot(appRoutes,{useHash:true}),

Where the appRoutes is your initial routing for the app, like so:

const appRoutes: Routes = [
    {
        path: '',
        redirectTo: '/PATH-TO-HOME',
        pathMatch: 'full'
    },
];

Then just make sure you have import { RouterModule, Routes } from '@angular/router';

You should not need to change anything inside the flask app. I will push up an updated version for you.

mmatlock-shr commented 6 years ago

Doing that will prevent the ability to do server side rendering with Angular though.

Location Strategy

Is there no way to have something as fundamental as browser refresh alongside history pushstate location strategy?

Additional information: When running ng serve, it works fine. It's only when using the Flask server and deploying via the dist/ folder with ng build --watch that this issue shows up.

mmatlock-shr commented 6 years ago

I have done the change, and this did not fix it, either in my project: image Or in the F_ng_skeleton: image (Note that the route doesn't seem to have changed on this one, even though I added the useHash config to it? Uncertain what's going on there...) image

I rebuilt the dist (it was on watch anyways, but I rebuilt it again just in case), and rebooted the server, refreshed the page with the cache disabled...hmmm...

(Additionally, for some reason our Flask server always renders the Angular twice, or does a force reload of the page upon initial load. Uncertain as to why...)

hawzie197 commented 6 years ago

@mmatlock-shr,

hmmm it worked for my django projects. make sure that your backend is calling the static routing still.

Angular allows you to do refresh on serve because you are only running the angular server. When you run the flask server, the routes are then pointed to the flask templates directory, which is why we set the template routing to static/dist in order to render angular's contents.

Angular has their own server routing set up because they will eventually be launching their own backend server, meaning we wont need flask, but for now, the hash routing is the only solution I have found to this problem.

I can try and get a working example going this weekend if I get a couple hours free.

Michael

mmatlock-shr commented 6 years ago

OK, so I have found the solution, with help from StackOverflow.

The issue is that you can't just redirect for '/' on Flask's side when using History Pushstate. You must also redirect on 404 errors (and possibly all HTTP errors > 399). So the solution turned out to be pretty simple for me:

# In the future, we may need to handle *all* HTTP errors in this way
# TODO: Will this handle static images and files properly?
@app.errorhandler(404)
def handle_angular_routes():
    return send_from_directory(app.template_folder, "index.html")  # 

@app.route("/", defaults={"path": ""}, methods=["GET", "POST"])
@app.route("/<path:path>", methods=["GET", "POST"])
def get_resource(path):  # pragma: no cover
    # If we are devmode, proxy through flask.
    if config.getKey("devmode") == True:
        raise Exception("Use ng build --prod --aot --watch, not ng serve.")
    # return the results
    return send_from_directory(app.template_folder, path)

I believe the reason using hash location strategy works is because the browser doesn't send a request for the new page back to the server the way that the push state strategy does.

hawzie197 commented 6 years ago

yes thats exactly it for hash routing. I do not have too much time in the next couple weeks with finals coming. If you would like to become a contributor and pull down the most recent code and implement this code that would be awesome.

Michael

mmatlock-shr commented 6 years ago

I will do so if I get the time, though it will probably be from my personal account rather than my work account, and it might take me a bit due to you using older version of flask and using the deprecated/defunct flask-script and manage.py method of bootstrapping Flask. (In our project, we use neither Flask Cli nor flask-script, and just run directly from server.py... and we additionally use Swagger/Flasgger, which has additional oddities in the setup portions).

So it might take me a bit to ensure I'm not stepping on anything. AFAIK it won't be a big issue, and I'll be able to knock it out quickly, but y'know, don't wanna promise too much. This weekend is pretty packed for me as well ^___^;