I'm struggling to capture URL query string parameters being passed to my angular2 app by a 3rd party API. The URL reads http://example.com?oauth_token=123
How can I capture the value of "oauth_token" inside a component? I'm happily using the component router for basic routing it's just the query string. I have made several attempts and my latest receiving component looks like
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
#Component({
template: ''
})
export class TwitterAuthorisedComponent implements OnInit {
private oauth_token:string;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
console.log('the oauth token is');
console.log( this.route.snapshot.params.oauth_token );
}
}
Any advice is appreciated.
** UPDATE
If I comment out all my routes the query parameters will stick however, the moment I include a route the query parameters are removed on page load. Below is a stripped down copy of my routes file with one route
import {NavigationComponent} from "./navigation/components/navigation.component";
import {TwitterAuthorisedComponent} from "./twitter/components/twitter-authorised.component";
import { provideRouter, RouterConfig } from '#angular/router';
export const routes: RouterConfig = [
{ path: '', component: TwitterAuthorisedComponent }
];
export const appRouterProviders = [
provideRouter(routes)
];
If I remove the route the query params stick. Any advice?
I you can catch the query prams using the bellow solution.
import { Router, Route, ActivatedRoute } from '#angular/router';
queryParams:string;
constructor(private router: Router, private actRoute: ActivatedRoute)
{
this.queryParams=
this.router.routerState.snapshot.root.queryParams["oauth_token"];
}
Using this you will get the value of oauth_token to queryParams. I think this seems fine for you
If you need to update the value queryParams, query parameter changes you need to add some more code. Its like below.
import { Router, Route, ActivatedRoute } from '#angular/router';
queryParams:string;
sub:any;
constructor(private router: Router, private actRoute: ActivatedRoute)
{
this.queryParams=
this.router.routerState.snapshot.root.queryParams["oauth_token"];
}
ngOnInit(){
this.sub = this.router.routerState.root.queryParams.subscribe(params => {
this.queryParams = params["oauth_token"];
});
}
Hope it will work for you.
Query parameters can be obtained through the Router service.
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
template: ''
})
export class TwitterAuthorisedComponent implements OnInit {
private oauth_token:string;
constructor(private router: Router) {}
ngOnInit() {
console.log('the oauth token is');
console.log( this.router.routerState.snapshot.queryParams.oauth_token );
}
}
You can do this by
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
template: ''
})
export class TwitterAuthorisedComponent implements OnInit {
sub: any; //new line added
private oauth_token:string;
constructor(private router: Router) {}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = +params['oauth_token'];
console.log('the oauth token is');
console.log(id);
});
}
}
Hope this will help. Thank you
Related
I'm new in Ionic 2 and I'm having troubles with passing data between pages. In my Home.ts file I have a global array that contains some numbers I calculated and i want to pass it to my Table.ts file, to show it in a HTML table with the *ngFor method.
this is the Function in Home.ts where i fill the array and try to push (i will skip the calculations, becacause i know they are correct).
`import { Component } from '#angular/core';
import { AlertController } from 'ionic-angular';
import { IonicPage,NavController, NavParams} from 'ionic-angular';
import {Table} from '../table/table';
export class HomePage {
averagesList: Array <number> =[];
constructor(public alerCtrl: AlertController,
public navCtrl: NavController,
public navParams: NavParams)
{}
Calculate(){
var Averages=[];
//Calculations on the 'Averages' Array
this.averagesList = Averages;
this.navCtrl.push(Table,this.averagesList);
}
}
So I try to print it in my Table.ts file but it gives me undefined result
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import {HomePage} from '../home/home';
#IonicPage()
#Component({
selector: 'page-table',
templateUrl: 'table.html',
})
export class Table{
constructor(public navCtrl: NavController, public navParams: NavParams) {
}
ionViewDidLoad() {
console.log(this.navParams.get('averagesList'));
}
}
I've tried to pass a let variable and it worked, so why doesn't it work with arrays?
Your mistake is using console.log(this.navParams.get('averagesList'));
Here 'averagesList' is the key.
To get it this way, you need to send as :
this.navCtrl.push(Table,{'averagesList' : this.averagesList});
Else:
if you directly send as
this.navCtrl.push(Table,this.averagesList);
You can retrieve value like so:
console.log(this.navParams.data);
you can use services to do so. Just like in angular2 you can import your service within the constructor and use the property like this.
import {OnInit} from '#angular/core';
import {someService} from ./somepath;
...
export class someClass implements OnInit{
let myTmpVar; //we will capture the shared data in this variable
constructor (private smService: someService){ ... }
ngOnInit{
this.myTmpVar = this.smService.SharedServiceData;
}
...
}
It's better to use service for passing nested data. In your case calculations object.
You can create messageService and listen to changes, something like below.
import {Injectable} from '#angular/core';
import {Observable} from 'rxjs';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class LocalMsgService {
private subject = new Subject();
sendMessage(message) {
this.subject.next(message);
}
clearMessage() {
this.subject.next();
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
}
Which can be used in your home.ts and table.ts pages as follows
Home.ts
//other imports comes here
import {LocalMsgService} from 'services/localMsg';
#Component({
selector: 'home-component',
templateUrl: 'home.html'
})
export class HomePage {
constructor( private msgService: LocalMsgService) {
}
dataToPass() {
console.log(this.averagesList);
this.msgService.sendMessage(this.averagesList);
}
}
Table.ts
//other imports comes here
import {LocalMsgService} from 'services/localMsg';
import {Subscription} from 'rxjs/Subscription';
#Component({
selector: 'page-table',
templateUrl: 'table.html',
})
export class TablePage{
items: any;
subscription: Subscription;
constructor(
public localMsgService : LocalMsgService) {
this.subscription = this.localMsgService.getMessage().subscribe(msg => {
this.items = msg;
});
}
}
I have a simple ionic 2 app.
Created at service
import { Storage } from '#ionic/storage';
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import { AlertController } from 'ionic-angular';
import { NavController, NavParams } from 'ionic-angular';
import { AgendaPage } from '../pages/agenda/agenda';
import { LoginPage } from '../pages/login/login';
import 'rxjs/add/operator/map';
#Injectable()
export class Auth {
constructor(public http: Http, public storage: Storage, public alertCtrl: AlertController, public navCtrl: NavController) {}
}
app.components.ts registration
import { Component, ViewChild } from '#angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';
import { Auth } from '../providers/auth';
import { Rides } from '../providers/rides';
import { AgendaPage } from '../pages/agenda/agenda';
import { LoginPage } from '../pages/login/login';
#Component({
templateUrl: 'app.html',
providers: [Auth, Rides]
})
export class MyApp {
#ViewChild(Nav) nav: Nav;
rootPage: any = LoginPage;
pages: Array<{title: string, component: any}>;
constructor(public platform: Platform) {
this.initializeApp();
// used for an example of ngFor and navigation
this.pages = [
{ title: 'Minha Agenda', component: AgendaPage }
];
}
initializeApp() {
this.platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
StatusBar.styleDefault();
Splashscreen.hide();
});
}
openPage(page) {
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.nav.setRoot(page.component);
}
}
Trying to inject it into a component
import { Component } from '#angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Auth } from '../../providers/auth';
/*
Generated class for the Agenda page.
See http://ionicframework.com/docs/v2/components/#navigation for more info on
Ionic pages and navigation.
*/
#Component({
selector: 'page-agenda',
templateUrl: 'agenda.html',
providers: [Auth]
})
export class AgendaPage {
openRides: any;
constructor(public navCtrl: NavController, public navParams: NavParams, private auth: Auth) {}
}
I get the following error:
Can't resolve all parameters for AgendaPage: (NavController,
NavParams, ?).
What I find strange is that I have a very similar other component where I can user the service without problems:
import { Component } from '#angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Auth } from '../../providers/auth';
/*
Generated class for the Login page.
See http://ionicframework.com/docs/v2/components/#navigation for more info on
Ionic pages and navigation.
*/
#Component({
selector: 'page-login',
templateUrl: 'login.html',
providers: [Auth]
})
export class LoginPage {
email: string;
password: string;
constructor(public navCtrl: NavController, public navParams: NavParams, private auth: Auth) {}
login() {
}
}
This works perfectly.
If it's an authentication service, you probably don't need to provide it at the lower levels of abstraction. It still needs to be imported to be used, but it doesn't need to be added to the providers[] in your lower tier components.
This may be causing the error, as for some reason it might not be able to provide an instance of Auth at that level of abstraction for whatever reason. Notably, I try not to use constructors on my services -- that may be causing the issue as well (or both issues together.)
The problem here is circular dependency. You are already importing the component in your service and want to inject the service in the same component. So angular does not know which to load first.
You need to look into forwardref.
In your component constructor,
constructor(...,#Inject(forwardref(()=>Auth))auth)
You might want to refer here for more.
I have a search bar in a header component.
Beneath that, I have a "router-outlet" in that same component.
The search bar (input txtfield), once enter is pressed, needs to send the search string (event.target.value) to the component that resides within the router-outlet beneath it so that it can run a method to return the results.
I have no clue what the best way is to achieve this.
UPDATED with code..
app.component.html:
<div class="white-container">
<input name="searchStr" [(ngModel)]="searchStr" (keyup.enter)="searchCourse($event)">
</div>
<router-outlet></router-outlet>
app.component.ts:
import { Component, OnInit } from '#angular/core';
import { CourseService } from './services/courses.service';
import { Course } from './Course';
#Component({
selector: 'my-app',
templateUrl: 'app.component.html',
providers: [CourseService]
})
export class AppComponent implements OnInit {
constructor(private _courseService: CourseService) {
}
searchCourse(event) {
// the user search string here...
}
}
/course-listings/course-listings.component.ts:
import { Component, OnInit } from '#angular/core';
import { CourseService } from './services/courses.service';
import { Course } from './Course';
#Component({
selector: 'app-course-listings',
templateUrl: './course-listings.component.html',
styleUrls: ['./course-listings.component.css'],
providers: [CourseService]
})
export class AppComponent implements OnInit {
course: Course[];
constructor(private _courseService: CourseService) {
}
searchCourse(evt) {
// This works once it's fired...
this._courseService.findCourse(evt)
.subscribe(courses => {
this.course = courses;
});
}
}
/services/courses.service.ts:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class CourseService {
constructor(private _http:Http) {
}
getCourses(search) {
return this._http.get('/api/v1/courses/'+search)
.map(res => res.json());
}
}
FIX FOUND
Günter Zöchbauer was correct. I used a service w/ subscribe and observables to do it. Thanks.
An event.subscriber would be required in the constructor to pass to router-outlet.
Similar to the answer in this Angular 2 router event listener.
So, once the click is done, the subscriber event will be executed based on on the navigationend event, then the value can be accessed.
Can we navigate/redirect to any login url (different host and application) or must we navigate/redirect only to url's within the routes of our application?
The example from the angular site suggests only application routes are permitted:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = state.url;
// Navigate to the login page
this.router.navigate(['/login']);
return false;
}
Have you tried navigateByUrl()?
See https://angular.io/docs/ts/latest/api/router/index/Router-class.html for usage.
But:
Why you need an "external" URL not part of your application?
I assume best practive would be an external authService instead of an seperate "Login-Page-Application". or such.
e.g.
import { Injectable } from '#angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router} from '#angular/router';
import {Observable} from "rxjs";
import {AuthService} from "../services/auth.service";
#Injectable()
export class LoginGuard implements CanActivate {
constructor(protected router: Router, protected authService: AuthService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
console.log('AuthGuard#canActivate called');
if (state.url !== '/login' && !this.authService.isLoggedIn()) {
this.router.navigate(['/login'], {queryParams: {redirectTo: state.url}});
return false;
}
return true;
}
}
import {Component} from "#angular/core";
import {ActivatedRoute, Router} from "#angular/router";
#Component({
templateUrl: "app/components/login/login.html"
})
export class LoginComponent {
constructor(public route: ActivatedRoute, public router: Router) { }
loginSuccessful() {
let redirect = this.route.snapshot.queryParams['redirect'];
let redirectUrl = redirect != null ? redirect : '/dashboard/';
console.log('redirect to: ' + redirect);
this.router.navigate([redirectUrl]);
}
}
I made an app with angular-cli, Immutable and Redux. I followed instructions from this article which does not describe how to get data. My app needs to initialize the Redux store with data from an asynchronous http call.
I have a component for listing the data which has a template. This component gets its data from the store which depends on a service which makes the http call. The http call works but the app throws an exception that indicates the listing component is trying to get the data before it has arrived.
My repository is here
A demo of the app is here
Error message:
main.js:21 TypeError: Cannot read property 'getState' of undefined
at a.get [as objections] (https://dancancro.github.io/bernierebuttals/main.js:18:22268)
at a._View_a0.detectChangesInternal (a.template.js:189:37)
at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
at a.detectViewChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13774)
at a._View_a_Host0.detectChangesInternal (a.template.js:34:8)
at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
at a.detectContentChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13588)
at a.detectChangesInternal (https://dancancro.github.io/bernierebuttals/main.js:32:13345)
at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
at a.detectViewChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13774)
Here are some relevant parts of the code: (I'm working on this. The repository contains the current code)
list.component.html
...
<ul id="objection-list" [sortablejs]="store.objections" [sortablejsOptions]="options" (update)="setTouched()">
<li *ngFor="let objection of store.objections">
<list-objection
[objection]="objection"
[editable]="editable"
(onEdit)="setTouched()"
(onReordered)="setReordered(objection)"
></list-objection>
</li>
</ul>
...
list.component.ts
import { Component, OnInit, ContentChildren, QueryList } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { SortablejsOptions, SORTABLEJS_DIRECTIVES } from 'angular-sortablejs';
import Immutable = require('immutable');
import { ObjectionComponent } from './objection/objection.component';
import { ObjectionModel } from '../objection';
import { ObjectionStore } from '../objection-store';
import { DataService } from '../data.service';
import { addObjection } from '../actions';
#Component({
moduleId: module.id,
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.css'],
providers: [ObjectionStore, DataService],
directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {
private sub: any;
editable: boolean = false;
touched: boolean = false;
expanded: boolean = false;
options: SortablejsOptions = {
disabled: false
};
objectionID: number;
constructor(
private store: ObjectionStore,
private route: ActivatedRoute) {}
...
}
objection-store.ts
import { Injectable } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import Immutable = require('immutable');
import { createStore } from 'redux';
import { ObjectionAction } from './actions';
import { reducer } from './reducer';
import { ObjectionModel } from './objection';
import { DataService } from './data.service';
#Injectable()
export class ObjectionStore {
private sub: any;
store: any;
constructor(
private dataService: DataService) {
this.store = createStore(reducer, Immutable.List<ObjectionModel>(objections.json()));
});
}
data.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import { ObjectionModel } from './objection';
import { Area } from './area';
let objectionsPromise;
#Injectable()
export class DataService {
result: Object;
combined: any;
error: Object;
getUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec?json=1';
postUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec';
static getObjection(objections: any[], id: number): ObjectionModel {
return objections.filter(function(objection) {
return objection.id === id
})[0];
}
constructor(private http: Http) {
objectionsPromise = this.http.get(this.getUrl).toPromise();
}
Basic Answer
To answer the immediate question, getting the objections from the server and putting them in a template can be simply done like this:
Use async pipe to bind an observable directly into the template. Async pipe 'unboxes' observables (promises too) and updates your template when they change.
<ul id="objection-list"
[sortablejs]="store.objections"
[sortablejsOptions]="options"
(update)="setTouched()">
<li *ngFor="let objection of objections | async">
<list-objection
[objection]="objection"
[editable]="editable"
(onEdit)="setTouched()"
(onReordered)="setReordered(objection)">
</list-objection>
</li>
</ul>
Use DataService to initialize this observable when your component is initialized:
#Component({
moduleId: module.id,
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.css'],
providers: [ObjectionStore, DataService],
directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {
// ...
private objections: Observable<ObjectionModel[]>;
constructor(
private dataService: DataService,
private route: ActivatedRoute) {}
ngOnInit() {
this.objections = this.dataService.getObjections();
}
// ...
Fix up your data service:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { ObjectionModel } from './objection';
#Injectable()
export class DataService {
result: Object;
combined: any;
error: Object;
getUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec?json=1';
postUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec';
getObjections(private http: Http): Observable<ObjectionModel[]> {
return this.http.get(this.getUrl) // returns an observable of the response
.map(response => response.json()); // transforms it into an observable of ObjectionModels
}
}
Notes on Redux
Note that this has all been done without Redux.
In general, I like Redux and I use it a lot. However in your example you seem to be doing a couple of unorthodox things:
You are creating a store in your ObservableStore service - this suggests to me that you are planning on having several stores in your app. One of the main principles of Redux is global immutable state, meaning that there is normally only one Redux store in an application.
You seem to by trying to fetch the initial data set from the server and then creating your store when the response has come back. It's not generally a good idea to couple the store creation to an HTTP request like this. Instead I recommend creating an empty store when you initialize your app, and then updating it via reducer when the HTTP request comes back.
You can do raw Redux in Angular 2, but you may find it a little frustrating getting it to work with Angular's observable-heavy APIs. Fortunately people (including me) have done this work for you in the form of Observable-oriented redux libraries like ng2-redux and ngrx/store
If you were to use ng2-redux, things would look more like this:
Top-level app component: build your store and initialize it:
import { NgRedux } from 'ng2-redux';
import { rootReducer } from './reducers';
#Component({ /* ... */ })
class App {
constructor(private ngRedux: NgRedux<any>) {
this.ngRedux.configureStore(rootReducer, {});
}
}
List component: bind your template to a selector from the store's current data. Also trigger a data fetch on initialization.
import { NgRedux, select } from 'ng2-redux';
#Component({
moduleId: module.id,
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.css'],
providers: [DataService],
directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {
// ...
// Magic selector from ng2-redux that makes an observable out
// of the 'objections' property of your store.
#select('objections') objections: Observable<ObjectionModel[]>;
constructor(
private ngRedux: NgRedux<any>,
private dataService: DataService) {}
ngOnInit() {
this.subscription = this.dataService.getObjections()
.subscribe(objections => this.ngRedux.dispatch({
type: FETCH_OBJECTIONS_OK,
payload: objections
},
error => this.ngRedux.dispatch({
type: FETCH_OBJECTIONS_ERROR,
error: error
});
)
}
}
OK... so how does the data actually get into the store? Via the reducer. Remember in redux store state can only be changed from a reducer.
export function objectionReducer(state = [], action) {
switch(action.type) {
case FETCH_OBJECTIONS_OK: return [ ...action.payload ];
case ADD_OBJECTION: return [ ...state, action.payload ];
// etc.
}
return state;
}
We can also track errors in a reducer too if we want, how you want to structure this is up to you.
export function errorReducer(state = {}, action) {
switch(action.type) {
case FETCH_OBJECTIONS_ERROR: return { objectionFetch: action.error }
}
}
Since we have one store, we modularize the reducers instead and compose them together:
import { combineReducers } from 'redux';
import { objectionReducer } from './objection.reducer';
import { errorReducer } from './error.reducer';
export const rootReducer = combineReducers({
objections: objectionReducer,
error: errorReducer
});
More to Learn/Disclosure
Disclosure: I am one of the authors of Ng2-Redux. However Ngrx/Store is also a great alternative for doing redux with Ng2, and while the implementation is different using it is very similar to what I have described above.
I and my colleagues also maintain a number of training resources on Angular2 and on Redux, which I will provide below:
A good Angular2-specific intro to redux: http://angular-2-training-book.rangle.io/handout/redux/
An intro to Observables: http://angular-2-training-book.rangle.io/handout/observables/
Ng2-Redux documentation: https://github.com/angular-redux/ng2-redux/blob/master/README.md
Your data service is wrong. you are missing the point of the promise/observable.
you should read angular documentation about http client. at least read this part:
https://angular.io/docs/ts/latest/guide/server-communication.html#!#promises
No http call in the constructor!
this is more like it:
getHeroes (): Promise<Hero[]> {
return this.http.get(this.heroesUrl)
.toPromise()
.catch(this.handleError);
}
After you get use to that, I really recommend you read some about Observables. much cleaner and advanced.