How to structure my states and routing in ui-router - angular-ui-router

I´m building a e-commerce site using angular1 and ui-router (1.0.0.beta3).
But I´m not sure how to setup this up.
This is what I have in mind.
const home = {
name: 'home',
url: '/',
views: {
header: 'header',
navbar: 'navbar',
sidenav: 'sideNav',
content: 'home'
}
};
const category = {
name: 'home.category',
url: '/{url}',
views: {
content: 'categoryPage'
}
};
const product = {
name: 'home.category.product',
url: '/{url}',
views: {
content: 'productPage'
}
};
we can´t control links that come from the cms it self, "/about-us" and "/category-x" can be a category or a cms-page so we added a route state where we can resolve the entity_type (product, category or cms-page)
.state('home.router', {
url: '/{url}?:{page:int}&:{limit:int}&:id',
params: {
limit: null,
category: null,
page: {
dynamic: true
},
id: {
dynamic: true
}
},
templateProvider: ['urlRewrite', function(urlRewrite) {
switch (urlRewrite.entity_type) {
case 'category':
return '<category-page limit="$stateParams.limit" page="$stateParams.page" category="{name: $resolve.urlRewrite.request_path, id: $resolve.urlRewrite.entity_id}"/>';
case 'product':
return '<product-page id="$resolve.urlRewrite.entity_id"/>';
case 'cms-page':
default:
return '<page url="$resolve.urlRewrite.target_path" />';
}
}],
resolve: {
urlRewrite: ['UrlRewrite', '$stateParams', function(UrlRewrite, $stateParams) {
return UrlRewrite.getUrlRewrite($stateParams.url);
}]
}
});
the problem is that category and route url patterns collide.
and we can´t really use parent/child inherits etc etc..
How should we go about resolving "unknown" urls?
Plunkr: http://plnkr.co/edit/gXzDO5j3arP8QCrpwL9k?p=preview

Let me provide you with the sample snippet. It will give you the idea.
appName.config(['$stateProvider','$urlRouterProvider', '$httpProvider' ,function ($stateProvider, $urlRouterProvider, $httpProvider) {
// $urlRouterProvider.otherwise('/login');
$urlRouterProvider.otherwise(function($injector, $location){
var state = $injector.get('$state');
var $localStorage = $injector.get('$localStorage');
if($localStorage.user){
return '/dashboard'
}else {
return '/login'
}
});
$stateProvider
.state('login', {
url: '/login',
views: {
'': {
templateProvider: function ($templateFactory, $localStorage) {
return $templateFactory.fromUrl(asset_path('angular/templates/base/login.html'));
},
controller: 'LoginCtrl'
}
}
})
$httpProvider.interceptors.push('Interceptor');
}]);

Related

Prisma2: How to solve n +1 Problem with Paljs

thx for any help.
Im using at the frontend the apollo-client and at the backend graphql-nexus,prisma2 and graphql-yoga server.
I want to solve the n + 1 problem with #paljs/plugins.
At the frontend I have a query posts like:
query posts{
posts {
id
favoritedBy(where: { id: { equals: $currentUserId } }) {
id
}
author {
id
avatar {
id
}
}
link {
id
}
games {
id
}
tags {
id
}
likes(where: { user: { id: { equals: $currentUserId } } }) {
id
}
}
}
Posts resolver:
import { PrismaSelect } from '#paljs/plugins'
export const posts = queryField('posts', {
type: 'Post',
list: true,
args: {
...
},
resolve: async (_parent, args, { prisma, request }, info) => {
const select = new PrismaSelect(info).value
let opArgs: FindManyPostArgs = {
take: 10,
orderBy: {
[args.orderBy]: 'desc',
},
...select
}
const post = await prisma.post.findMany(opArgs)
//The result I want to return with the "sub-models" like likes, author tags...
console.log(JSON.stringify(post, undefined, 2))
return post
},
})
I logging the queries
const prisma = new PrismaClient({
log: ['query'],
})
My Problem: With PrismaSelect, I have 5 queries more than without and If I check the request-time at the frontend I need 300-400ms longer with PrismaSelect. So what I'm doing wrong?
I saw in the #paljs/plugins doc the select in the context. Maybe that is my mistake. How can I use the select in the context?
Here ist my Context:
import { PrismaClient, PrismaClientOptions } from '#prisma/client'
import { PubSub } from 'graphql-yoga'
import { PrismaDelete, onDeleteArgs } from '#paljs/plugins'
class Prisma extends PrismaClient {
constructor(options?: PrismaClientOptions) {
super(options)
}
async onDelete(args: onDeleteArgs) {
const prismaDelete = new PrismaDelete(this)
await prismaDelete.onDelete(args)
}
}
export const prisma = new PrismaClient({
log: ['query'],
})
export const pubsub = new PubSub()
export interface Context {
prisma: PrismaClient
request: any
pubsub: PubSub
}
export function createContext(request: any): Context {
return { prisma, request, pubsub }
}
You need to know that to use my PrismaSelect plugin you need to remove the nexus-prisma-plugin package and use my Pal.js CLI to create your CRUD and ObjectType for nexus and using #paljs/nexus plugin to add in mackSchema function
import { makeSchema } from '#nexus/schema';
import * as types from './graphql';
import { paljs } from '#paljs/nexus'; // import our plugin
export const schema = makeSchema({
types,
plugins: [paljs()],// here our plugin don't use nexus-prisma-plugin
outputs: {
schema: __dirname + '/generated/schema.graphql',
typegen: __dirname + '/generated/nexus.ts',
},
typegenAutoConfig: {
sources: [
{
source: require.resolve('./context'),
alias: 'Context',
},
],
contextType: 'Context.Context',
},
});
Now add this type to your Context
export interface Context {
prisma: PrismaClient
request: any
pubsub: PubSub
select: any // here our select type
}
export function createContext(request: any): Context {
// our paljs plugin will add select object before resolver
return { prisma, request, pubsub, select: {} }
}
after you add our plugin your query will log like this
extendType({
type: 'Query',
definition(t) {
t.field('findOneUser', {
type: 'User',
nullable: true,
args: {
where: arg({
type: 'UserWhereUniqueInput',
nullable: false,
}),
},
resolve(_, { where }, { prisma, select }) {
// our plugin add select object into context for you
return prisma.user.findOne({
where,
...select,
});
},
});
},
});
Can you please try to use my pal c command to start an example from my list and try your schema and make tests with it
It is working, thx Ahmed your plugin is AWESOME!!!!!
I changed my Post-Object from
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.authorId()
t.model.tags()
t.model.games()
t.model.link()
t.model.report()
t.model.notifications()
t.model.author()
t.model.favoritedBy({
filtering: {
id: true,
},
})
t.model.likes({
filtering: {
user: true,
},
})
}
})
to
const Post = objectType({
name: 'Post',
definition(t) {
t.string('id')
t.field('tags', {
nullable: false,
list: [true],
type: 'Tag',
resolve(parent: any) {
return parent['tags']
},
})
t.field('games', {
list: [true],
type: 'Game',
resolve(parent: any) {
return parent['games']
},
})
t.field('link', {
type: 'Link',
nullable: true,
resolve(parent: any) {
return parent['link']
},
})
t.field('notifications', {
list: [true],
type: 'Notification',
resolve(parent: any) {
return parent['notifications']
},
})
t.field('author', {
nullable: false,
type: 'User',
resolve(parent: any) {
return parent['author']
},
})
t.field('favoritedBy', {
nullable: false,
list: [true],
type: 'User',
args: {
where: 'UserWhereInput',
},
resolve(parent: any) {
return parent['favoritedBy']
},
})
t.field('likes', {
list: [true],
type: 'Like',
args: {
where: 'LikeWhereInput',
},
resolve(parent: any) {
return parent['likes']
},
})
},
})
And I also used the nexus-prisma-plugin and paljs-plugin at the same time

Laravel vue won't return meta tags

I provided meta in my vue-router but it doesn't return in my html
Code
router sample
{
path: '/',
name: 'home',
component: Home,
meta: {
title: 'Home Page - Welcome',
metaTags: [
{
name: 'description',
content: 'The home page of our example app.'
},
{
property: 'og:description',
content: 'The home page of our example app.'
}
]
}
},
{
path: '/login',
name: 'login',
component: Login,
meta: { title: 'Login' }
},
beforeeach
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (localStorage.getItem('myApp.jwt') == null) {
next({
path: '/login',
params: { nextUrl: to.fullPath }
})
} else {
let user = JSON.parse(localStorage.getItem('myApp.user'))
if (to.matched.some(record => record.meta.is_admin)) {
if (user.is_admin == 1) {
next()
}
else {
next({ name: 'userboard' })
}
}
next()
}
} else {
next()
}
});
This is how my page head is look like:
and no sign of meta in browser as well:
Questions
How to fix this issue?
How can I get title for dynamic pages like single post components?
..........
You would need to add a way for the tags to be updated in the DOM as vue-router won't do this for you out-of-the-box.
You could try adding an after hook with something like:
router.afterEach((to, from) => {
document.title = to.meta && to.meta.title ? to.meta.title : ''; // You can add a default here
Array.from(document.querySelectorAll('[data-vue-meta]')).map(el => el.parentNode.removeChild(el));
if (to.meta && to.meta.metaTags) {
to.meta.metaTags.map(tagDef => {
let tag = document.createElement('meta');
Object.keys(tagDef).forEach(key => tag.setAttribute(key, tagDef[key]));
tag.setAttribute('data-vue-meta', '');
return tag;
}).forEach(tag => document.head.appendChild(tag));
}
});

Send data to components in VueJS + Vue Router

I can't pass data from app to compenent. After render it shows only clear html, without data from vue. All works, but without data((
My code from app.js:
var Series = Vue.component('Series', require('./components/Series.vue'),{
props: {
series: {
type: Array,
default: []
},
images: {
type: Array,
default: []
},
showPhotos: {
type: Boolean,
default: false
}
}
});
const Bar = { template: '<div>bar</div>' }
const Foo = { template: '<div>foo</div>' }
const routes = [
{ path: '/weedings', component: Series },
{ path: '/', component: Foo },
{ path: '/family', component: Foo },
{ path: '/other', component: Foo },
{ path: '/videos', component: Bar },
{ path: '/blog', component: Bar },
{ path: '/about', component: Foo },
{ path: '/contacts', component: Bar }
]
const router = new VueRouter({
routes // short for routes: routes
});
var app = new Vue({
el: "#app",
router,
data: {
series: [],
currentSerie: 0,
images: [],
showPhotos: false
},
methods: {
fetchSeries: function(){
this.$http.get('/api/fetchSeries').then((response) => {
this.series = response.body
}, (response) => {
alert("fail")
});
},
fetchPhotos: function(id){
this.showPhotos = false;
this.$http.get('/api/fetchPhotos/'+id).then((response) => {
this.images = response.body
this.showPhotos = true;
$("html, body").animate({ scrollTop: 60 }, "500");
}, (response) => {
alert("fail")
});
},
photos: function(id){
this.fetchPhotos(id)
}
},
created: function(){
this.fetchSeries()
setTimeout(function(){ require('./custom'); }, 1000);
}
});
When I dont use vue-router, all works fine. And i know i can pass data to components in this way: <my-component :artribute="value"></my-component>, but in this case IDK how to pass data.
Use function mode like this:
{
path: '/weedings',
component: Series,
props: () => (
{ series: app.series, images: app.images, showPhotos: app.showPhotos }
)
}
Check working example in JSFiddle.
Note: You have to use vuex as a centralized store for all the components in an application to be able to implement more complex scenarios.
In your routes declaration you should add props
`const routes = [
{ path: '/weedings', component: Series, props: true}]`
Here mentioned: Passing props to Vue.js components instantiated by Vue-router

Parameter in $stateParams are undefined in resolve

I have a page showing a list of contact and clicking on one of the contacts in the view should switch to detail state as below:
dashboard-contacts-controller.js
vm.viewContact = function(contactId) {
console.log("Load contact " + contactId);
$state.go("app.dashboards_contact", {"id": contactId});
}
contacts-module.js
.state('app.dashboards_contact', {
url: '/dashboard-contact/:id',
views: {
'content#app': {
templateUrl: 'app/main/apps/dashboards/contacts/about/about.html',
controller: 'DashboardContactController as vm'
}
},
resolve: {
contact: ['DashboardContactsDataService', function($stateParams, DashboardContactsDataService) {
console.log($stateParams.id);
return DashboardContactsDataService.get($stateParams.id);
}
]
},
bodyClass: 'dashboard-contact'
});
$stateParams.id in resolve is always undefined.
You didnt inject $stateParams in resolve. Shouldn't it be
resolve: {
contact: ['$stateParams', DashboardContactsDataService', function($stateParams, DashboardContactsDataService) {
console.log($stateParams.id);
return DashboardContactsDataService.get($stateParams.id);
}
]
},

Marionette - throws error on `removeRegions` how to solve it

In my app, i have the regions as header,content,footer - in which on the login page, I don't want to use the header, and footer. for that, on onRender i remove the regions what i don't want to be.
But I am getting an error saying: Cannot read property 'empty' of undefined.
here is my template : (i use jade )
div#wrapper
script(type='text/template', id="appTemplate")
div#header
div#content
div#footer
script(type='text/template', id="loginTemplate")
div this is login template
here is my layout.js:
socialApp.AppLayout = Backbone.Marionette.LayoutView.extend({
el:'#wrapper',
template:'#appTemplate',
regions: {
header : '#header',
content : '#content',
footer : '#footer'
},
onRender : function () {
this.removeRegion("header", "#header"); //i am removing header alone here.
}
});
here is my controller.js
socialApp.loginController = Marionette.Controller.extend({
_initialize:function(){
this.loginView = new loginView({model:new loginModel});
this.layout.onRender(); //calling onRender from here...
this.layout.content.show(this.loginView);
}
});
But it's all not working. any one help me the correct way please?
You should never call methods that are prefixed with on manually. Those are there for your code to react to given events, in this case that the view’s render method was invoked.
I would suggest that you instead of trying to remove and then later re-add regions, you create two different layouts. Then when your router hits the login route, you render LoginLayout into your App’s root region, and for other routes, the ‘normal’ layout. Here’s how I solved something similar:
app.js:
var App = new Marionette.Application;
App.addRegions({ root: '#acme' });
// Instantiate User model
App.addInitializer(function()
{
this.user = new UserModel;
});
// Render App layout
App.addInitializer(function()
{
this.layout = this.user.get('id') ? new ContentLayoutView({ identifier: 'content' }) : new UserLayoutView({ identifier: 'user' });
this.root.show(this.layout);
// And let the routers decide what goes in the content region of each layout
this.router = {
content: new ContentRouter,
user: new UserRouter
};
});
layout/content.js
var ContentLayout = Marionette.LayoutView.extend(
{
identifier: 'content',
template: ContentLayoutTemplate,
regions: {
content: '[data-region="content"]',
panelLeft: '[data-region="panel-left"]',
panelRight: '[data-region="panel-right"]'
},
initialize: function()
{
this.content.once('show', function(view)
{
this.panelLeft.show(new PanelLeftView);
this.panelRight.show(new PanelRightView);
}.bind(this));
}
});
layout/user.js
var UserLayout = Marionette.LayoutView.extend(
{
identifier: 'user',
template: UserLayoutTemplate,
regions: {
content: '[data-region="content"]'
}
});
router/content.js
var ContentRouter = Marionette.AppRouter.extend(
{
routes: {
'(/)': '...'
},
createLayout: function(callback)
{
if(App.root.currentView.options.identifier != 'content')
{
var layout = new ContentLayoutView({ identifier: 'content' });
this.region = layout.content;
this.listenTo(layout, 'show', callback);
App.root.show(layout);
}
else
{
this.region = App.root.currentView.content;
callback();
}
},
execute: function(callback, args)
{
if(App.user.get('id'))
{
this.createLayout(function()
{
callback.apply(this, args);
}.bind(this));
}
else
App.router.user.navigate('login', true);
}
});
router/user.js
var UserRouter = Marionette.AppRouter.extend(
{
routes: {
'login(/)': 'showLogin',
'logout(/)': 'showLogout'
},
createLayout: function(callback)
{
if(App.root.currentView.options.identifier != 'user')
{
var layout = new UserLayoutView({ identifier: 'user' });
this.region = layout.content;
this.listenTo(layout, 'show', callback);
App.root.show(layout);
}
else
{
this.region = App.root.currentView.content;
callback();
}
},
execute: function(callback, args)
{
this.createLayout(function()
{
callback.apply(this, args);
}.bind(this));
},
showLogin: function()
{
var LoginView = require('view/detail/login');
this.region.show(new LoginView);
},
showLogout: function()
{
var LogoutView = require('view/detail/logout');
this.region.show(new LogoutView);
}
});

Resources