Title not changing | Angular 4 - javascript

I'm trying to override the document title on a route.
This is the route with a default title.
{
path: 'artikel/:id/:slug',
component: ArticleComponent,
data: {title: 'Article', routeType: RouteType.ARTICLE,
description: metaDescription},
resolve: {error: ErrorResolverService, article: ArticleResolveService},
}
I am using an ArticleResolverService to get the article from the ID and then apply a new Title.
resolve(route: ActivatedRouteSnapshot): Observable<Article> {
let id = route.paramMap.get('id');
return this.as.getArticle(id).take(1).map(article => {
if (article) {
//this seems to set the title temporarely (flickering visible)
this.ts.setTitle(article.title);
return article;
}
return null;
});
}
Since this approach doesn't quite work I tried setting the title in the ArticleComponent, which is the target of the route. (This happens in ngOnInit)
this.route.data.subscribe((data:{article: Article}) => {
this.article = data.article;
//this seems to set the title temporarely (flickering visible)
this.ts.setTitle(this.article.title);
//using this in browser console works permanently
window['setTitle'] = (t) => this.ts.setTitle(t);
});
Whatever I am doing, everytime I load the page i see the flickering of the wanted title but then it gets instantly reset to the default title (if I don't use a default title it will just show the page URL in the title bar, also just after flickering of the wanted title).
How do i effectively set a permanent title for this page?

The last step from your question is not required (window['setTitle']). Works just fine with the approach below. The default title will show until the setTitle function is called, then it will stay at the new title
Import Title service
import { Title } from '#angular/platform-browser';
Inject it
constructor(
...
private titleService: Title,
) {...}
Use it in the ngOnInit
this.titleService.setTitle("Some custom title" + this.newInfoFromAPI);

Related

Asynchronous method doesn't work the first time

I'm new in Firebase. I'm using Firestore database and Ionic, I have this problem with an asynchronous call and I can't solve it. Basically in the item variable goes the data that I have saved in the firestore database. But when I want to show them, through a button, in a new html page a strange thing happens, in the url the passed parameter appears and disappears immediately and nothing works anymore. I had a similar problem in the past that I solved using the angular pipe "async" , but in this case it doesn't even work.
In detail, I have a list of items in a component:
ngOnInit() {
this.itemService.getItemsList().subscribe((res)=>{
this.Tasks = res.map((t) => {
return {
id: t.payload.doc.id,
...t.payload.doc.data() as TODO
};
})
});
}
and in item.service.ts I have defined the function:
constructor(
private db: AngularFireDatabase,
private ngFirestore: AngularFirestore,
private router: Router
) { }
getItemsList() {
return this.ngFirestore.collection('items').snapshotChanges();
}
getItem(id: string) {
return this.ngFirestore.collection('items').doc(id).valueChanges();
}
For each item I have a button to show the detail:
<ion-card *ngFor="let item of Tasks" lines="full">
....
<ion-button routerLinkActive="tab-selected" [routerLink]="['/tabs/item/',item.id]" fill="outline" slot="end">View</ion-button>
In component itemsDescription.ts I have:
ngOnInit() {
this.route.params.subscribe(params => {
this.id = params['id'];
});
this.itemService.getItem(this.id).subscribe((data)=>{
this.item=data;
});
}
Finally in html page:
<ion-card-header>
<ion-card-title>{{item.id}}</ion-card-title>
</ion-card-header>
<ion-icon name="pin" slot="start"></ion-icon>
<ion-label>{{item.Scadenza.toDate() | date:'dd/MM/yy'}}</ion-label>
<ion-card-content>{{item.Descrizione}}</ion-card-content>
The Scadenza and Descrizione information are shown, instead id is not. Also the url should be tabs/items/:id but when I click on the button to show the item information, the passed parameter immediately disappears and only tabs/items is displayed. If I remove the data into {{}}, the parameter from the url doesn't disappear
SOLVED
I followed this guide https://forum.ionicframework.com/t/async-data-to-child-page-with-ionic5/184197. So putting ? , for example {{item?.id}} now everything works correctly
Your nested code order not right. You will get the value of id after subscription.
Check this Code:
ngOnInit() {
this.route.params.subscribe(params => {
this.id = params['id'];
this.profileService.getItem(this.id).subscribe((data)=>{
this.item=data;
});
});
}

Link component interpolation error nextjs

I am getting this error in Next.js:
Error: The provided 'href' (/subject/[subject]) value is missing query values (subject) to be interpolated properly. Read more: https://err.sh/vercel/next.js/href-interpolation-failed`.
I have a dynamic page set up as /subject/[subject].tsx. Now in my navigation I have:
<Link href={'/subject/${subject}'} passHref><a>{subject}</a></Link>
It works fine when I access the page with different paths but when I am pressing on a button on the page it throws the error above which I imagine is because the component rerenders. If you go to the page in the error it says: Note: this error will only show when the next/link component is clicked not when only rendered.
I have tried to look for a solution and I tried doing:
<Link href={{pathname: '/subject/[subject]', query: {subject: subject}}}></Link>
but nothing changed. I read the docs and it seems that the as prop is only an optional decorator that is not used anymore so I fail to see how that can help me.
I got the same issue when trying to redirect user to locale. I did it in useEffect. After investigate I discovered that on first render router.query is empty, so it's missing required field id. I fix it by using router.isReady
export const useUserLanguageRoute = () => {
const router = useRouter()
useEffect(() => {
const {
locales = [],
locale,
asPath,
defaultLocale,
pathname,
query,
isReady // here it is
} = router
const browserLanguage = window.navigator.language.slice(0, 2)
const shouldChangeLocale =
isReady && // and here I use it
locale !== browserLanguage
&& locale === defaultLocale
&& locales.includes(browserLanguage)
if (shouldChangeLocale) {
router.push(
{
pathname,
query,
},
asPath,
{ locale: browserLanguage }
)
}
}, [router])
}
Another possible solution could be redirect using the router.push function:
const myRedirectFunction = function () {
if (typeof window !== 'undefined') {
router.push({
pathname: router.pathname,
query: {...router.query, myqueryparam: 'myvalue'},
})
}
}
return (
<>
<button onClick={() => {myRedirectFunction()}}> Continue </button>
</>
)
It is important to include ...router.query because there is where the [dynamic] current value is included, so we need to keep it.
Reference: https://github.com/vercel/next.js/blob/master/errors/href-interpolation-failed.md
You better do a null || undefined check in prior.
#Bentasy, Like you rightly pointed out, any re-render of the page is expecting you to pass in the dynamic route params again. The way I avoided page re-rendering with subsequent clicks on the page was to replace Link tag on the page with a label tag. I was using the Next Link tag to navigate between the tabs on this image which generated this same error. So I replaced the Link tags with label tags and the error was solved.
I was having the same problem, this is how I solved it.
While pushing something to the router, we need to give the old values ​​in it.
In that:
const router = useRouter()
router.push({
...router,
pathname: '/your-pathname'
})
try add this function, it work for me:
export async function getServerSideProps(context) {
return {
props: {},
};
}

nuxt.js get default head in vue.js component

I am trying to get the head object that is configured by nuxt.config.js in a vue layout. In order to show the same title in an app bar as the page title.
I know that you can alter the page title with the head function in a vue component. But is it also possible to retrieve this information somehow?
<script>
export default {
data () {
return {
title: head.titleTemplate // possible?
}
},
head () {
// here it is possible to change it but how about getting it?
}
}
</script>
Another approach could be to get some data out of an page in the nuxt.config.js. But I think this is not how the hierarchy is structured.
Thanks for you help I am just starting to use javascript to code a website :)
(If I understand you correctly) You can use the changed callback to keep track of the latest meta info used (and thus the title).
Example:
head() {
return {
changed: (info) => {
this.title = info.title;
console.log(info, info.title);
},
};
},
data() {
return {
title: '',
};
},
In nuxt.config.js before export I have setted variable with a string of the title.
Then added it to the head section and create a new env section:
https://nuxtjs.org/api/configuration-env/
const title = `Site title`
export default {
head: {
title
},
env: {
title
}
}
This how I'm getting the title in any Vue component:
export default {
computed: {
title () {
return process.env.title
}
},
}
This helps you to keep your original title in process.env.title, even if you will want to change head.title dynamically.
Did anyone found a better solution maybe? :)

Angular 4 On Meta Tags Rendered

Is there an event triggered when Meta tags are rendered?
In my angular 4 app I have some dynamic routes like:
const routes: Routes = [
{ path: 'products/:slug', component: ProductComponent }
];
In the ProductComponent (product.component.ts) I get the meta data like below:
ngOnInit() {
let currentSlug = this.route.snapshot.paramMap.get('slug');
//Makes ajax call to server to get meta data
this.MyMetaService.getMeta(currentSlug)
.subscribe((metadata) = > {
//Set the page title
this.title.setTitle(metadata.title);
this.meta.addTags([
{name: 'keywords', content: metadata.keywords},
{name: 'description', content: metadata.description}
])
});
}
Which works fine! Now, I would like to know when the meta tags are fully rendered on page however so far could not figure out how!
I have tried the following but failed:
ngAfterViewInit() {
const title = $(document).find("title").text();
console.log('page title is: ', title); //title is still not updated!
}
Surely setTimeout works but I am not sure it's not that reliable:
setTimeout(() => {
const title = $(document).find("title").text();
console.log('page title is: ', title); //Got correct title here!
}, 200);
The reason behind this is, you're making an ajax call to retrieve meta tags, that takes time to make roundtrip from server(perhaps few milliseconds). And ngAfterViewInit component hook gets called as soon as inner component tree gets rendered.
I don't think so setTimeout made it working solely, it is working because you had 200ms time mentioned.

Refreshing data through constructor in Angular 2 / Ionic 2

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.. :\

Categories

Resources