Angular 2 dynamic routing - wordpress

I'm converting an existing db (Wordpress) driven site to an Angular 2 front end, keeping the backend as is. I'm at the routing stage and I want to create the routes from JSON data pulled from the db.
So, I've created a service to pull the data from the db and a separate routing module to register the routes:
app-routing.module.ts
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { NavService } from './nav.service';
var navService: NavService;
const routes: Routes = navService.getRoutes();
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRouting{}
nav.service.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class SiteNavService{
private _navUrl = 'pathtofile/?action=ajax_site_nav';
constructor(private _http: Http){}
getNav(){
return this._http.get(this._navUrl)
.map( res => res.json())
.toPromise();
}
}
The service is registered in the app.module.ts providers array and the routing module in the imports array. I can't get nav.service to work in the routing module file.

I've just now been working on trying to add routes dynamically to angular 2 and found that getting the current routes with
this.router.config
and then changing them and applying the changes with
this.router.resetConfig(newRoutes);
Worked pretty well
I have my demo project here that might help where team members are dynamically added from json https://github.com/davidejones/angular2-routertest

This is now possible using Xo for Angular: https://wordpress.org/plugins/xo-for-angular/
Xo creates dynamic routes based on your WordPress content, posts, pages, etc.
The secret sauce making this possible is the use of loadChildren in the Angular Route definition to lazy load a particular module. This is necessary as otherwise Route expects a component from an import that isn't realistic.
Example of this output from the docs site API: https://angularxo.io/xo-api/routes/get
Then in the Angular App using the angular-xo module these routes are loaded and updated by calling: router.resetConfig(response.routes);
Taken from this file: https://github.com/WarriorRocker/angular-xo-node/blob/master/lib/src/services/route.service.ts
Full disclosure, I am the author of the above plugin.
More docs (work in progress): https://angularxo.io
Example theme: https://github.com/WarriorRocker/angular-xo-material

Related

Vue3 using script setup, how to access this.$router.push()

I am new to Vue3 and am struggling with some examples. I have a simple login component that should redirect to another page but isn't.
Here is my template:
And here is my script:
The problem is that the this.$router is not available, because this is undefined. All the examples that I can find use this coding pattern, but the this variable is undefined in the script setup coding pattern as far as I can tell. What is the alternative?
Thanks.
First off import the useRouter composable from vue-router, then use router to access the method.
import { useRouter } from 'vue-router';
const router = useRouter()
function login() {
router.push({ path: 'your-path-here' })
}
This is the equivalent of Options API's this.$router.push.

Does Next.js not need browser history when using Redux?

I'm trying to migrate my React app to Next.js. I keep having below error from export const history = createBrowserHistory(); in my configureStore.js.
Invariant Violation: Browser history needs a DOM
configureStore.js
import { createStore, combineReducers, compose, applyMiddleware } from "redux";
import { connectRouter, routerMiddleware } from "connected-react-router";
import { createBrowserHistory } from "history";
import thunk from "redux-thunk";
import user from "./modules/user";
import stores from "./modules/stores";
import info from "./modules/info";
export const history = createBrowserHistory();
const middlewares = [thunk, routerMiddleware(history)];
const reducer = combineReducers({
user,
stores,
info,
router: connectRouter(history)
});
export default function configureStore(preloadedState) {
return createStore(
reducer,
preloadedState,
compose(
applyMiddleware(
...middlewares
)
)
);
}
I've found that many things need to be changed when migrating React to Next.js as Next is a framework which requires its own code architecture as well as the difference between SSR and CSR. When I studied Next.js tutorials, there is a routing section which says that routing is differentiated based on CSR or SSR. Does that mean I cannot use browser history in Next.js? That's what I'm guessting. I'm still confused.
You are right. There is different between SSR and CSR. Next.js use Next Router for routing for CSR and if you need custom SSR, then you should ask for some help from frameworks like express.
By doing the CSR, nextjs will remember browser history and back button works as expected. However if you need to change the route to a very diffrernt route you can use any of these solutions:
import Router from 'next/router';
...
Router .push('/about');
Or
import Link from 'next/link';
...
<Link href="/about"><a>about</a></Link>
and if you need to do some extra work before routing then you should use:
Router.beforePopState(({ url, as, options }) => {...}
Migration would take some time and you need to remember next.js will take over the charge for routing and browser history automatically. Unless you customise it.

How do you snapshot test components with actions from Redux connect?

I want to snapshot test a react component that dispatches a redux action in its componentDidMount() handler. The action is given to the component through Redux's connect().
How should I mock the action?
Right now the action is imported in the component (see Newsfeed.js below). So how would I swap it out for a mock action in my test?
I'm using redux-thunk for my async actions, but that shouldn't matter much.
app/components/Newsfeed.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
// An action that makes an async request to get data
import { loadNewsfeedStories } from '../actions/Newsfeed';
class Newsfeed extends Component {
componentDidMount(){
this.props.loadNewsfeedStories();
}
...
}
export default connect(state => ({
stories: state.newsfeed.stories
}),
{
loadNewsfeedStories
})(Newsfeed)
app/tests/Newsfeed.test.js
import React from 'react';
import { Provider } from 'react-redux';
// This creates a mockStore using my reducers and a saved JSON state.
import { mockStore } from './MockState';
// The component to test
import Newsfeed from '../components/Newsfeed';
test('Newsfeed matches snapshot', () => {
const wrapper = mount(
<Provider store={mockStore}>
<Newsfeed />
</Provider>
);
expect(wrapper).toMatchSnapshot();
});
Possible Solutions
Export the unconnected component, and manually pass in all props & mock actions. Will be a lot of extra coding compared to just using the mockStore & provider. Also we won't be testing the 'connected' component.
Use something like Nock? Seems to intercept HTTP calls, so the ajax requests wouldn't actually go anywhere.
For axios, there is a lib called moxios - https://github.com/mzabriskie/moxios
You could try exporting a different connected component, but with a mocked "mapDispatchToProps" function. This way, no action will get through.
But personally, when testing components. I write a mock reducer, these should not change the state but record all dispatched actions (Jests mock functions are very useful for this). This way, you can also test if the correct action is dispatched when clicking a button,...
It's a bad idea to test the component, redux store, and mock http requests at the same time because unit tests should be small.
Another option would be to avoid executing business logic in componentDidMount.
Instead of writing
componentDidMount(){
this.props.loadNewsfeedStories();
}
You can move this logic to the redux module (depends on libs you use).
If you use only redux-thunk without libraries like saga or redux-logic, you can use this https://github.com/Rezonans/redux-async-connect.
but record all dispatched actions
Redux thunks are hard to test. Your mock dispatcher will receive only an array of functions. And you can't verify if a function was created by loadNewsfeedStories() or by someAnotherThunk().

How to populate variable from service after component has initialized angular2

I have a routed application that loads and works fine aside from the fact that I have an *ngIf element that does not show up on route change UNLESS I reload the page. I have a token variable from a service class where I store it in local storage, and I want to show my logout button when the token is not null.
When the site loads, it sets the token value to null which hides the button (expected behavior) but when logging in and seeing the token to the guid, the variable doesn't show a token value unless I reload the page which reinitializes the header component.
Abbreviated code below.
Import { Component } from '#angular/core';
Import { globalService } from './shared/globalService';
#Component({
selector: 'header-ele',
template: ` <div *ngIf="loginToken != null"><button>logout</button></div>
<router-outlet></router-outlet>`
})
export class headerComponent {
loginToken:any;
constructor(globalService: globalService){
this.logonToken = globalService.getUser(); //this either returns null or a token string
}
}
Then I have another component that changes the route which all works fine
//I have all of my correct imports and all works
export class loginComponent {
login(){
// I pass login params and get success
this.globalService.setUser(returnedData.LogonToken)
}
}
And in globalService I set logonToken = returnedData.LogonToken, BUT the button in my headerComponent doesn't show up unless I reload the page. So, I'm wondering if there is a way to reinitialize the headerComponent on route change success to get the token from globalService in the constructor function, or if there is a better way to share that parameter between the globalService and the headerComponent.
Abbreviated code due to submitting from mobile, but should get the idea.
Still learning the ins-and-outs of angular 2.
Your problem is that this.logonToken is only getting the value once in the constructor. So when you logout, the user token in your globalService is really null but not reflected in your header.component. One solution is to use observables to your header component and subscribe to your globalService. Or you directly use the global service variable to your template like this
<div *ngIf="globalService.getUser()"><button>logout</button></div>
Hope this helps.
According to Angular2 official documentation you can use ngOnInit function but I'll try to give you an example:
import { Component, OnInit } from '#angular/core';
import { Hero } from '../hero';
import { HeroService } from '../services/hero.service';
import { HeroDetailComponent } from '../hero-detail/hero-detail.component';
#Component({
selector: 'app-dashboard-component',
templateUrl: './dashboard-component.component.html',
styleUrls: ['./dashboard-component.component.css'],
providers: [HeroDetailComponent]
})
export class DashboardComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) {}
getHeroes(): void {
this.heroService.getHeroes().then(
(heroes) => this.heroes = heroes.slice(1,5)
);
}
ngOnInit(): void {
this.getHeroes();
}
}
This is, by the way, the example developed by Google's folks, ngOnInit will be invoked when you instantiate the component. Hope this answer your question and of course helps you. Cheers, sigfried.
Solution 1: Refactor your service so that it returns an observable, promise or event, then on your component make use of the async-pipe like so: <div *ngIf="(loginToken | async) != null">.
Solution 2: #Input the loginToken from a higher level component and Angular will run the change detection when the value changes.
Either way I would move the logic inside the constructor to a lifecycle hook like ngOnInit or ngOnChanges.

Can't access Meteor.user() property

I've installed a Meteor phone authentication package mys:accounts-phone, which should add a phone.number subfield into users collection. I try to access this field as follows:
Meteor.user().phone.number
but typescript shows error
Property 'phone' does not exist on type 'User'.
On the other hand, I have custom props in users.profile, and can easily access them in this way.
Insecure is not yet removed. Autopublish is ON.
this happens sometime when our angular component is initialized but our meteor data is not reached from server.
try to use user injection in place of Meteor.user()
import {Component} from "#angular/core";
import { InjectUser } from 'angular2-meteor-accounts-ui';//<--**** import this****
#Component({
selector: "login-buttons",
template
})
#InjectUser('user') //<--*** add this***
export class LoginButtonsComponent {
user: Meteor.User; //<--*** add this ***
constructor(private router: Router) {}
}
now in user variable you will have all values of Meteor.User
if you want to print in html part use this
<div *ngIf="user">
{{user.phone.number}}
</div>
don't forget to install
meteor add accounts-password
meteor npm install --save angular2-meteor-accounts-ui
and in app.module.ts file
import { AccountsModule } from 'angular2-meteor-accounts-ui';
#NgModule({
imports: [
... other modules here
AccountsModule
],
hope this will work. if this not work let me know i will tell you one other solution
Got probable answer from Meteor docs.
It explains why username property appears. By default, Meteor publish only a number of fields considered to be public. To exposure any additional fields they must be published explicitly.
Not yet have time to test, but think it should work.
The other reason, with the same sympthoms, when publication code do not executed at server side. Try to put
Meteor.startup(() => {
// code to run on server at startup
Meteor.publish('userData', function() {
if(!this.userId) return null;
return Meteor.users.find(this.userId
//, {fields: {lastname: 1,}}
);
});
});
in a file within /server folder of your application.

Resources