In my Angular 6 app, there are certain pages that do not respond
appropriately when I click "back". The site will instead spawn
multiple components, instead of redirecting the user back to the
single component they just went to.
For the faulty components, I made the component.html page be one
single line like such as:
// home-page.component.html
home
and
// admin-page.component.html
admin
And then the component.ts page is using default code as well.
On my app.component.html, I just have the following:
<router-outlet></router-outlet>
Now when I go on the home page (via <a
routerLink="/admin">Admin></a>), I correctly see this (more or
less)in my HTML when I inspect the site. And note this is just the RESULTING HTML that appears when I right click - view page source etc... I know my routing is setup correctly as this whole thing works in Google Chrome, but not in Firefox.
<html>
<head>...</head>
<body>
<app-root>
<router-outlet>
<app-admin-page>admin</app-admin-page>
</router-outlet>
</app-root>
</body> </html>
But when I now press "back", I see the below
<html>
<head>...</head>
<body>
<app-root>
<router-outlet>
<app-home-page>home</app-home-page>
<app-admin-page>admin</app-admin-page>
</router-outlet>
</app-root>
</body> </html>
When I pressed "back", it should of DELETED the
<app-admin-page>admin</app-admin-page> and just kept the new
<app-home-page>home</app-home-page>, but it keeps both. I can then
press "forward" and then it'll have 3 components. Any ideas what is
going on here? Note that if I'm on the 'admin' page and click the 'home' link (which does a routerLink thing), it works correctly. It's just the back button messing up.
You are mixing child components and routing. For any particular component, you should use one or the other.
There should be no components defined between the <router-outlet></router-outlet> tags.
Notice in your code above:
<router-outlet>
<app-admin-page>admin</app-admin-page>
</router-outlet>
So either display both components as child components like this:
<app-root>
<app-home-page>home</app-home-page>
<app-admin-page>admin</app-admin-page>
</app-root>
This will show both components, one above the other.
OR
Use routing:
<app-root>
<router-outlet></router-outlet>
</app-root>
This will show one component at a time in this location. Use a route configuration to define which components to display in the router outlet.
RouterModule.forRoot([
{ path: 'home', component: HomeComponent },
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'admin', component: AdminComponent }
]),
I had the same issue, where under certain circumstances when I hit the "Back" button in the browser, it would combine the current component with the one defined for the updated URL hash. I was able to resolve it by adding this in my constructor of each component:
constructor(private ngZone: NgZone, private router: Router, private location: PlatformLocation, private network: NetworkService) {
location.onPopState(() => {
this.ngZone.run(() => this.router.navigateByUrl(location.hash.replace('#/', ''))).then();
});
}
It then properly navigated to a preceding component without combining both on the page.
Related
I am creating a project. I've to work with many routes and tabs inside it. My problem is that I want my browser to remember that which tab was opened before refreshing the page.Suppose I have 3 tabs on 1 page and I opened the 2nd or 3rd tab, than I refresh the page it kick me back to 1st tab. How to prevent this.Is their any solution ?
Use child routes. Each tab would content a <router-view> element, which corresponds to a page within a page.
See vue-router docs on Nested Routes
const routes = [
{
path: '/my-page',
component: MainPage,
children: [
{
path: 'tab1',
component: Tab1Content,
},
{
path: 'tab2',
component: Tab2Content,
},
],
},
]
<!-- On /my-page/tab-2, Tab2Content will be rendered in the <router-view> -->
<template>
<v-tab>
<router-view /> <!-- Sub route -->
</v-tab>
</template>
I feel like there should be a very simple solution to this and I can't seem to find one. I would like to embed code on my Next.js website, however, it will only work if I refresh the page. It does not load when I navigate to it via router/navbar link.
I tried to render it in useEffect() and useState(), however, the <script> doesn't seem to want to run in the useEffect() hook. I have not found a successful way to get dirty with it and use vanilla JavaScript in getServerSideProps() as suggested in another post, but I did find next/script in the documentation. This actually treats the script mostly how I need it to as far as when it renders. There is only one issue...
It does not render in the location I put it in the DOM. Instead it renders at the bottom of the webpage (even below the footer). I tried creating a component and then inserting it but that did not work either. This is because not only is it at the bottom of the current page, it's loading at the bottom of every page.
Any help is appreciated. Thank you!
Embedded component:
import Script from "next/script";
export default function paintCalculator() {
return (
<Script
async
type="text/javascript"
id="paint-calculator-tool"
data-type="dotdash-tool"
data-vertical="thespruce"
src="https://www.thespruce.com/static/5.174.0/static/components/widgets/iframe-embed/embed.min.js?id=paint-calculator-tool"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `!function(){var e,i,n,a,t=document.getElementsByTagName("script");[].forEach.call(t,function(t){"dotdash-tool"===t.getAttribute("data-type")&&(e=t.parentElement,i=document.createElement("iframe"),n=t.getAttribute("id"),a=t.getAttribute("data-vertical"),i.src="https://www."+a+".com/tools/"+n,i.style.border="none",i.id=n,i.style.width="100%",t.nextSibling&&t.nextSibling.id===n||e.insertBefore(i,t.nextSibling))}),window.addEventListener("message",function(e){var t=document.getElementsByTagName("iframe");[].forEach.call(t,function(t){t.getAttribute("id")===e.data.embedId&&(t.style.height=e.data.height+10+"px")})},!1)}();`,
}}
/>
);
}
Page with imported component:
import styles from "../styles/Home.module.css";
import React from "react";
import PaintCalculator from "../components/PaintCalculator";
export default function paintCalculator() {
return (
<div className={styles.pageContainer}>
<div>
<h1>Paint Calculator</h1>
<PaintCalculator />
</div>
</div>
);
}
I try to create a custom component for my application with emberJS, I have followed the quickstart tutoriel and now I try to create a upload button as a component.
It seems I don't have code issues but my component don't render on my main page. I have use the ember component generator to create it
here my upload-button.hbs :
<button type="button">{{#buttonText}}</button>
now my upload-button.js :
import Component from '#ember/component';
export default Component.extend({
actions: {
showExplorer(){
alert()
}
}
});
for the moment I simply put a alert() method in showExplorer()
and now my main page, application.hbs :
<upload-button #buttonText="Uploader un fichier" {{action "showExplorer"}}/>
{{outlet}}
I expect to see my button but I just have a blank page with no code error.
I suspect that's a really stupid mistake but I can't find it.
Glad you decided to try out Ember.js :) Your upload-button.hbs and upload-button.js file looks good. However, there are a couple of issues here.
The component, when invoked using the angle-bracket syntax (<... />), the name should be CamelCased to distinguish between HTML tags and Ember components. Hence, we need to invoke the upload-button component as <UploadButton />.
You defined your action, showExplorer, inside the component (upload-button.js) file, but, referenced in the application.hbs file. The data in the backing component file can only be accessed inside the component's .hbs file because of the isolated nature of the component architecture. Also, we can only attach an action using {{action}} modifier to a DOM element and not to the component itself. So,
we need to remove the action binding from application.hbs file,
{{!-- application.hbs --}}
<UploadButton #buttonText="Uploader un fichier"/>
and add the same inside the upload-button.hbs file:
{{!-- upload-button.hbs --}}
<button type="button" {{action "showExplorer"}}>{{#buttonText}}</button>
A working model can be found in this CodeSandbox
Hope you find learning Ember, a fun process!
On Durandal, I have a login page that's styled different from my other pages, so taking the advice I saw on some of the posts here, I've set up main.js to do a:
app.setRoot('viewmodels/login');
And on the login page, I provide the below method in login.js that resets the root:
loginRedirect: function() {
app.setRoot('viewmodels/shell');
}
The idea is that the user should go to the login page by default, and once logged in, I will invoke the method loginRedirect, which sets shell.html to be the root and thus reloads the content, and he should be able to navigate other pages from there.
Here's my login.html:
<div class="login">
<div data-bind="compose:'header-nav-plain.html'"></div>
<div class="container">
<!-- Content here-->
</div>
</div>
<div data-bind="compose:'viewmodels/footer'"></div>
<a class="go-inner-pages" data-bind="click: loginRedirect" href="#">Test link to go to 'inner' pages</a>
And here's my shell.html, that is the doorway to all other pages:
<div>
<div data-bind="compose:'viewmodels/header-nav'"></div>
<div class="container">
<div data-bind="compose:'viewmodels/site-nav'"></div>
<div data-bind="router"></div>
</div>
<div data-bind="compose:'viewmodels/footer'"></div>
</div>
You can see that shell.html is slightly different in structure from login.html, in that it binds to a different header-nav.html, and also a site-nav.html. (They share the same footer.html.) So on the login page, when I click on the test link that calls the loginRedirect method, it sets the root of the app to be shell.html, which will load the default page based on the parameters passed to router.map, as defined in shell.js:
activate: function(){
//Initializing router
router.map([
{ route: '', moduleId: 'viewmodels/dashboard', title: 'Dashboard', nav: true }
])
.buildNavigationModel();
//More code
return router.activate();
But, when I click on the test link and invoke loginRedirect, the new dashboard content is loaded, but the new header-nav and site-nav data binding didn't happen. I checked the inspector, and see that the structure has indeed changed to that of shell.html (as opposed to login.html), only data binding for header-nav and site-nav didn't happen. I.e. here's the new html after invoking loginRedirect:
<div>
<div data-bind="compose:'viewmodels/header-nav'"></div><!--header nav content not bound-->
<div class="container">
<div data-bind="compose:'viewmodels/site-nav'"></div><!--site nav content not bound-->
<div data-bind="router"><div class="durandal-wrapper" data-view="views/dashboard" data-active-view="true">
<!-- Dashboard content successfully loaded -->
</div><!--end data-bind="router"-->
</div><!--end .container-->
<div data-bind="compose:'viewmodels/footer'">
<!--footer content loaded-->
</div>
And from the Console (see below), one can see that while the shell and dashboard are bound, (even footer is bound a second time), header-nav and site-nav are not.
Does anyone have any idea why this is happening and what I might be doing wrong? Thanks in advance for your attention, hope to hear from someone soon. Cheers.
For view-only bindings, you would bind this way (we do the same as you, so I'm copying and pasting our code):
compose: {view: 'shellLeftFooter.html'}"
For views bound to viewModels, you bind this way:
compose: {model: 'viewmodels/navigation/shellNavigation'}
The default viewLocator will use the following convention:
If a view is specified with no model, then the view must be identified with the .html extension, as I have done in the former example, and it will not be bound to a viewModel;
If a model is specified with no view, as I have done in the latter example, then the model file is assumed to have a .js extension, and it is also assumed that a view exists in a folder named views under the app directory, with the same name as the model, but with an .html extension.
These aren't the only two conventions, but they are the ones germane to this discussion. To put the above in different words, make sure you have both a viewmodels and a views directory that are siblings of each other under the app directory. Make sure you give the viewModel and the view identical names, differing only in their extensions (.js for the former and .html for the latter).
I'm seeing all of your .html and .js files in the code you posted, so I'm sure what your intention is in each case (view only or model-view-viewModel). Also, your compose binding is not quite to standard.
I have ember routes set up like so:
App.Router.map(function() {
this.resource("subreddit", { path: "/r/:subreddit_id" }, function() {
this.resource('link', { path: '/:link_id'} );
});
});
But I want the to view each link in a completely separate template. In other words, I want to render a different block of html, rather than render the link html into subreddit's {{outlet}}.
When you hit /r/xxx in the browser Ember will look for two templates related to subreddit. First it looks for 'subreddit' and then it looks for 'subreddit/index'. If 'subreddit' if found, then it is rendered and then if 'subreddit/index' is also found, it is rendered into the {{outlet}} of 'subreddit'. If 'subreddit' is not found Ember will just move on to 'subreddit/index'. The 'subreddit' template is also rendered for any sub-paths that are nested under /subreddit, such as the 'link' template for /r/xxx/yyy. 'subreddit' is kind of like a "per model layout" template that allows you to wrap (decorate) all of the sub-path templates.
Something like this should let you keep your nested routes, and allow you to use Ember default behavior.
<script type="text/x-handlebars" data-template-name="subreddit">
<p>A static header bar for this subreddit could go here, or not.</p>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="subreddit/index">
<p>
All of the details of the subreddit go here, this will be rendered
into the {{outlet}} of "subreddit".
</p>
</script>
<script type="text/x-handlebars" data-template-name="link">
<p>
Link view goes here. This template will replace "subreddit/index"
in the {{outlet}} of "subreddit".
</p>
</script>
If you want to render the links in separate templates you should not nest your routes:
App.Router.map(function() {
this.resource("subreddit", { path: "/r/:subreddit_id" });
this.resource('link', { path: '/:link_id'});
});
A different approach you can take could be to use named outlet's and override renderTemplate of the corresponding route and render the template in the place of the named outlet's, have a look here for more info on that: http://emberjs.com/guides/routing/rendering-a-template/
Hope it helps.