AngularJS 2 Typescript interface - interface

I have a service for handling users operations and an interface for the user object.
user.service.ts
import {Injectable} from 'angular2/core';
export interface User {
name: string;
email?: string;
picture?: string;
}
#Injectable()
export class UserService {
me: User;
constructor() {
}
setUser(user: User) {
this.me = user;
}
}
In my login component I try to set the user with the profile returned from the login service but I get this error:
Property 'firstName' does not exist on type '{}'.
login.component.ts
import {Component} from 'angular2/core';
import {User, UserService} from './services/user.service';
import {LinkedinService} from './services/linkedin.service';
declare const IN: any;
console.log('`Login` component loaded asynchronously');
#Component({
selector: 'Login',
providers: [
UserService,
LinkedinService
],
template: require('./login.html')
})
export class LoginComponent {
me: User;
constructor(public linkedinService: LinkedinService, public userService: UserService) {
this.me = userService.me;
}
ngOnInit() {
console.log('hello `Login` component');
}
login() {
this.linkedinService.login()
.then(() => this.linkedinService.getMe()
.then(profile => this.userService.setUser({ name: profile.firstName })));
}
}
linkedin.service.ts
import {Injectable} from 'angular2/core';
declare const IN: any;
#Injectable()
export class LinkedinService {
constructor() {
IN.init({
api_key: 'xxxxxxxxxxx',
authorize: true
});
}
login() {
return new Promise((resolve, reject) => {
IN.User.authorize(() => resolve());
});
}
getMe() {
return new Promise((resolve, reject) => {
IN.API.Profile('me').result((profile) => resolve(profile.values[0]));
});
}
}
I'm trying to import the User interface from UserService and use inside the LoginComponent but I don't know what I'm doing wrong. Any idea? I am not sure if I have to use the User interface inside the LoginComponent, is that right?

Narrow in on the code :
.then(() => this.linkedinService.getMe())
.then(profile => this.userService.setUser({ name: profile.firstName })));
The type of profile is driven by the response of this.linkedinService.getMe(). Seems like it is something like Promise<{}>. It does not have the member firstName. Hence the error:
Property 'firstName' does not exist on type '{}'.
Fix
Check to the code / signatures of linkedinService. This has nothing to do with the user.service.ts file that the question contains 🌹
Update
Focus in on the code:
getMe() {
return new Promise((resolve, reject) => {
IN.API.Profile('me').result((profile) => resolve(profile.values[0]));
});
}
The value returned is driven by what is being passed to resolve. So make sure profile.values[0] has the right type. Alternatively provide the hint to the compiler:
getMe() {
return new Promise<{firstName:string}>((resolve, reject) => {
IN.API.Profile('me').result((profile) => resolve(profile.values[0]));
});
}

Related

How do I pass dynamic param in nestjs facebook strategy callback url

How do i pass some dynamic params in the facebook login callback url?
I have different types of users (differentiated by a 'type' param) signing up using facebook login. I have created a facebook auth strategy using passport-facebook which works fine.
However after authentication, when callback url is called, i need to know which type of user requested the signup.
I'm guessing i can pass a param when defining the callback url
something like this
http://localhost:3000/auth/facebook/callback/type1
http://localhost:3000/auth/facebook/callback/type2
How do I pass a dynamic value into the FacebookStrategy??
or whats the possible workaround to achieve this?
// PassportStrategy.ts
#Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
clientID: 'MYID',
clientSecret: 'MYSCRET',
callbackURL: "http://localhost:3000/auth/facebook/callback",
profileFields: ['id', 'displayName', 'emails', 'photos']
});
}
async validate(accessToken: any, refreshToken: any, profile: any) {
return {
name: profile.displayName,
email: profile.emails[0].value,
provider: "facebook",
providerId: profile.id,
photo: profile.photos[0].value
}
}
}
// auth controller
#Controller('auth')
export class AuthController {
constructor(
#Inject(forwardRef(() => AuthService)) private readonly authService: AuthService,
) { }
#Get('/facebook')
#UseGuards(AuthGuard('facebook'))
async facebookAuth(#Request() req) {
return
}
#UseGuards(AuthGuard('facebook'))
#Get('/facebook/callback')
async facebookCallback(#Request() req) {
return this.authService.login(req.user);
}
}
Basically i want to be able to call "/auth/facebook/:type" and pass the type value in the callback url defined in the Strategy
and callback endpoint to be something like "/auth/facebook/callback/:type"
so when i call the authservice.login function i can pass that 'type' and decide which type of user to be created if its the first time signup
Guide me if my approach is wrong. Thanks
I have been dealing recently with a similar issue here is my approach. Probably is not the best but works for now.
import { Inject, Injectable, Logger } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import passport = require('passport');
import { Strategy } from 'passport-facebook';
#Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') {
private readonly logger = new Logger(FacebookStrategy.name);
constructor(
#Inject('FACEBOOK_STRATEGY_CONFIG')
private readonly facebookStrategyConfig,
) {
super(
facebookStrategyConfig,
async (
request: any,
accessToken: string,
refreshToken: string,
profile: any,
done,
) => {
this.logger.log(profile);
// take the state from the request query params
const { state } = request.query;
this.logger.log(state);
// register user
// return callback
return done(null, profile);
},
);
passport.use(this);
}
}
import { Controller, Get, HttpStatus, Inject, Param, Query, Req } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { Redirect } from '#nestjsplus/redirect';
#Controller('auth')
export class AuthController {
#Inject('ConfigService')
private readonly configService: ConfigService;
#Get(':provider/callback')
#Redirect()
async socialCallback(#Req() req, #Param('provider') provider: string, #Query('state') state: string) {
// here you can use the provider and the state
return {
statusCode: HttpStatus.FOUND,
url: `${this.configService.get('FRONTEND_HOST')}/dashboard`,
};
}
}
import { MiddlewareConsumer, Module, NestModule } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { AuthController } from './auth.controller';
import { FacebookStrategy } from './facebook.strategy';
import passport = require('passport');
const facebookStrategyConfigFactory = {
provide: 'FACEBOOK_STRATEGY_CONFIG',
useFactory: (configService: ConfigService) => {
return {
clientID: `${configService.get('FACEBOOK_CLIENT_ID')}`,
clientSecret: `${configService.get('FACEBOOK_CLIENT_SECRET')}`,
callbackURL: `${configService.get('FACEBOOK_OAUTH_REDIRECT_URI')}/callback`,
profileFields: ['id', 'displayName', 'link', 'photos', 'emails', 'name'],
passReqToCallback: true,
};
},
inject: [ConfigService],
};
#Module({
controllers: [AuthController],
providers: [facebookStrategyConfigFactory, FacebookStrategy],
})
export class AuthModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
const facebookLoginOptions = {
session: false,
scope: ['email'],
state: null,
};
consumer
.apply((req: any, res: any, next: () => void) => {
const {
query: { state },
} = req;
facebookLoginOptions.state = state;
next();
}, passport.authenticate('facebook', facebookLoginOptions))
.forRoutes('auth/facebook/*');
}
}
Now let me explain a little bit :D. The trick is in the middleware configuration.
const facebookLoginOptions = {
session: false,
scope: ['email'],
state: null,
};
consumer
.apply((req: any, res: any, next: () => void) => {
const {
query: { state },
} = req;
facebookLoginOptions.state = state;
next();
}, passport.authenticate('facebook', facebookLoginOptions))
.forRoutes('auth/facebook/*');
So, oAuth has this feature that you can pass a state param through the login flow.
By extracting the passport option in a variable we can change the state param dynamically by applying another middleware before the passport one.
In this way, you can call now http://localhost:3000/auth/facebook/login?state=anything-you-want
and this state query param will be passed through the strategy and also in the callback call.
I have also created a git repo with the example: https://github.com/lupu60/passport-dynamic-state
Another approach: the need was to dynamically set server url. It gets it using Context/Request.
// Custom Guard:
export const DynamicAuthGuard = (type?: string): Type<IAuthGuard> => {
const endpoint = `auth/${type}/redirect`
return class extends AuthGuard(type) {
getAuthenticateOptions(context: ExecutionContext) {
const httpContext: HttpArgumentsHost = context.switchToHttp()
const req: Request = httpContext.getRequest<Request>()
const serverURL = `${req.protocol}://${req.get('host')}`
const args = 'foo=bar'
const callbackURL = `${serverURL}/${endpoint}?${args}`
return {callbackURL}
}
}
}
// In controller 'auth':
#UseGuards(DynamicAuthGuard('facebook')) // or any passport strategy
#Get('facebook/redirect')
async facebookRedirect(#Req() req: Request, #Res() res: Response) {
// ...
}

angular2-mdl table component with server side data

I experiment with Angular 2 - Material Design Lite especially with the table component but I can not figure out how would I pass data from server on ajax request. Here is the example provided for table initialisation.
How would I pass data from restAPI to table component?
Here I have a kind of working example. I placed the initial data on my Component Init method where I call the DataService which populates the table. I'm not sure if is the right workaround but at this point I have data in table.
import { Component, ViewChild, ViewContainerRef, OnInit, Pipe, PipeTransform } from '#angular/core';
import { MdDialog, MdDialogConfig, MdIcon } from "#angular/material";
import { AuthenticationService, DialogsService, DataService } from '../../../services/';
import { RouterModule, Routes, Router } from '#angular/router';
import {
IMdlTableModelItem,
MdlDefaultTableModel
} from 'angular2-mdl';
export interface ITableItem extends IMdlTableModelItem {
username: string;
email: string;
role: string;
unitPrice: number;
}
#Component({
selector: 'employees',
templateUrl: 'app/layouts/secure/employees/employees.html',
providers: [DialogsService, MdIcon]
})
export class EmployeesComponent implements OnInit {
public message: string;
public employees: any[];
public result: any;
public showSearchBar: false;
public tableData:[ITableItem];
public selected;
public tableModel = new MdlDefaultTableModel([
{key:'username', name:'Username', sortable:true},
{key:'email', name:'Email', sortable:true},
{key:'role', name:'Role', sortable:true},
{key:'status', name:'Status', sortable:true},
{key:'unitPrice', name:'Test', numeric:true}
]);
constructor(
private dialogsService: DialogsService,
public viewContainerRef: ViewContainerRef,
private _dataService : DataService,
private router: Router
) {
}
openDialog() {
this.dialogsService
.confirm('User Form', 'Are you sure you want to do this?', this.viewContainerRef)
.subscribe(res => this.result = res);
}
toggleSearch() {
console.log(this)
}
ngOnInit() {
var self = this;
this._dataService
.GetAll('employees')
.subscribe( data => {
data = Object.keys(data).map((key)=>{ return data[key]})
this.employees = data;
this.tableData = data;
this.tableModel.addAll(this.tableData);
}, error => console.log(error),
() => function ( data ) {
this.tableData = this.employees;
this.tableModel.addAll(this.tableData);
this.selected = this.tableData.filter( data => data.selected);
},
);
}
generateArray(obj){
return Object.keys(obj).map((key)=>{ return obj[key]});
}
selectionChanged($event){
this.selected = $event.value;
}
}
#fefe made it a little more difficult than it had to be, at least with the current version. The magic of the as keyword can do the heavy lifting.
For example my class setup looks like:
import...
export interface IUnreadMessage extends IMdlTableModelItem {
messageId: number;
subject: string;
from: string;
}
#Component ...
export class ...
private unreadMessagesTable = new MdlDefaultTableModel([
{key: 'messageId', name: 'Message ID'},
{key: 'subject', name: 'Subject'},
{key: 'from', name: 'From'}
]);
Then in my ajax call I have:
...ajax call here).subscribe(value => {
const messages = value as Array<IUnreadMessage>;
this.unreadMessagesTable.addAll(messages);
},
error => {
...error handler here...
});
Make sure your interface is EXACTLY (including case) the same as your returned ajax data and it should hook right up!

How to perform async validation using reactive/model-driven forms in Angular 2

I have an email input and I want to create a validator to check, through an API, if the entered email it's already in the database.
So, I have:
A validator directive
import { Directive, forwardRef } from '#angular/core';
import { Http } from '#angular/http';
import { NG_ASYNC_VALIDATORS, FormControl } from '#angular/forms';
export function validateExistentEmailFactory(http: Http) {
return (c: FormControl) => {
return new Promise((resolve, reject) => {
let observable: any = http.get('/api/check?email=' + c.value).map((response) => {
return response.json().account_exists;
});
observable.subscribe(exist => {
if (exist) {
resolve({ existentEmail: true });
} else {
resolve(null);
}
});
});
};
}
#Directive({
selector: '[validateExistentEmail][ngModel],[validateExistentEmail][formControl]',
providers: [
Http,
{ provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => ExistentEmailValidator), multi: true },
],
})
export class ExistentEmailValidator {
private validator: Function;
constructor(
private http: Http
) {
this.validator = validateExistentEmailFactory(http);
}
public validate(c: FormControl) {
return this.validator(c);
}
}
A component
import { Component } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { ExistentEmailValidator } from '../../directives/existent-email-validator';
#Component({
selector: 'user-account',
template: require<string>('./user-account.component.html'),
})
export class UserAccountComponent {
private registrationForm: FormGroup;
private registrationFormBuilder: FormBuilder;
private existentEmailValidator: ExistentEmailValidator;
constructor(
registrationFormBuilder: FormBuilder,
existentEmailValidator: ExistentEmailValidator
) {
this.registrationFormBuilder = registrationFormBuilder;
this.existentEmailValidator = existentEmailValidator;
this.initRegistrationForm();
}
private initRegistrationForm() {
this.registrationForm = this.registrationFormBuilder.group({
email: ['', [this.existentEmailValidator]],
});
}
}
And a template
<form novalidate [formGroup]="registrationForm">
<input type="text" [formControl]="registrationForm.controls.email" name="registration_email" />
</form>
A've made other validator this way (without the async part) and works well. I think te problem it's related with the promise. I'm pretty sure the code inside observable.subscribe it's running fine.
What am I missing?
I'm using angular v2.1
Pretty sure your problem is this line:
...
email: ['', [this.existentEmailValidator]],
...
You're passing your async validator to the synchronous validators array, I think the way it should be is this:
...
email: ['', [], [this.existentEmailValidator]],
...
It would probably be more obvious if you'd use the new FormGroup(...) syntax instead of FormBuilder.

Angular 2 data service

I'm building an observable data service based on the following article: https://coryrylan.com/blog/angular-2-observable-data-services
In the article he used an array as an example, here I will use the user object since I'm developing the user service.
Here's what I got:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Events, SqlStorage, Storage } from 'ionic-angular';
import { Subject } from 'rxjs/Subject';
export interface DataStore {
user: Object
}
#Injectable()
export class UserService {
private baseUrl: string;
private storage: Storage;
private _user$: Subject<Object>;
private dataStore: DataStore;
constructor(
private http: Http
) {
this.baseUrl = 'http://localhost:3000';
this.storage = new Storage(SqlStorage);
this._user$ = <Subject<Object>>new Subject();
this.dataStore = {
user: { name: '' }
};
}
set user$(user: Object) {
this.storage.set('user', JSON.stringify(user));
this.dataStore.user = user;
this._user$.next(this.dataStore.user);
}
get user$() {
return this._user$.asObservable();
}
loadUser() {
return this.storage.get('user').then(
((user: string): Object => {
this.dataStore.user = JSON.parse(user);
this._user$.next(this.dataStore.user);
return this.dataStore.user;
})
);
}
login(accessToken: string) {
return this.http
.post('http://localhost:3000/login', { access_token: accessToken })
.retry(2)
.map((res: Response): any => res.json());
}
logout(): void {
this.storage.remove('user');
}
}
To authenticate I call the login() function and set the user data if everything ok.
this.userService.login(this.data.accessToken)
.subscribe(
(user: Object) => {
this.userService.user$ = user;
this.nav.setRoot(EventListComponent);
},
(error: Object) => console.log(error)
);
I feel it is better set the user data inside the service. I could do the following:
login(accessToken: string) {
return this.http
.post('http://localhost:3000/login', {
access_token: accessToken
})
.retry(2)
.map((res: Response): any => res.json())
.subscribe(
(user: Object) => {
this.userService.user$ = user;
this.nav.setRoot(EventListComponent);
},
(error: Object) => console.log(error)
);
}
But I won't be able to subscribe to the login() function in the component since it's already subscribed. How could I redirect the user if everything ok or show an alert if anything goes wrong in the component but setting the user inside the service?
In the main component I load the user data and set the rootPage:
this.userService.loadUser().then(
(user: Object) => this.rootPage = EventListComponent,
(error: Object) => this.rootPage = LoginComponent
);
I thought that calling the loadUser() function at this time I would not have to call it again, but I have to call it in all components that I need the user data:
this.user = this.userService.user$;
this.userService.loadUser();
I don't think the service is the way it should, what could I improve? Is there any better way to achieve what I want? Any example or idea?

Ionic 2 HTTP request not working - Angular 2

Hi I'm trying to do a simple Http GET request, but can't get it to work in ionic v2 Beta...
here is my app.js:
import {App, Platform} from 'ionic-angular';
import {TabsPage} from './pages/tabs/tabs';
import {HTTP_BINDINGS} from 'angular2/http';
#App({
template: '<ion-nav [root]="rootPage"></ion-nav>',
providers: [HTTP_BINDINGS],
config: {} // http://ionicframework.com/docs/v2/api/config/Config/
})
export class MyApp {
static get parameters() {
return [[Platform]];
}
constructor(platform) {
this.rootPage = TabsPage;
platform.ready().then(() => {
});
}
}
and this is my page1.js:
import {Page} from 'ionic-angular';
import {Http} from 'angular2/http';
#Page({
templateUrl: 'build/pages/page1/page1.html'
})
export class Page1 {
constructor(http:Http) {
this.mget = http.get("https://httpbin.org/ip")
.subscribe(data => {
var alert = Alert.create({
title: "Your IP Address",
subTitle: data.json().origin,
buttons: ["close"]
});
this.nav.present(alert);
}, error => {
console.log(JSON.stringify(error.json()));
});
}
}
When adding http:Http to the constructor -> constructor(http:Http) the whole app goes blank in browser...
And I get an error in Console:
Error: Cannot find module "../page1/page1"
I've also tried this in Page1.js:
export class Page1 {
constructor() {
}
makeGetRequest() {
this.http.get("https://httpbin.org/ip")
.subscribe(data => {
var alert = Alert.create({
title: "Your IP Address",
subTitle: data.json().origin,
buttons: ["close"]
});
this.nav.present(alert);
}, error => {
console.log(JSON.stringify(error.json()));
console.log('yolo')
alert('hello');
});
}
}
and then call makeGetRequest() on (click) in page1.html
but it returns these exeptions:
EXCEPTION: Error during evaluation of "click"
ORIGINAL EXCEPTION: TypeError: this.http is undefined
please help!
:)
-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
THIS IS THE SOLUTION:
page1.js:
import {Page} from 'ionic-angular';
import {Http} from 'angular2/http';
#Page({
templateUrl: 'build/pages/page1/page1.html'
})
export class Page1 {
static get parameters(){
return [Http];
}
constructor(http) {
this.http = http;
this.mget = this.http.get("https://httpbin.org/ip")
.subscribe(data => {
console.log(data);
}, error => {
console.log('faild');
});
}
}
app.js:
import {App, Platform} from 'ionic-angular';
import {TabsPage} from './pages/tabs/tabs';
import { HTTP_PROVIDERS } from 'angular2/http';
#App({
template: '<ion-nav [root]="rootPage"></ion-nav>',
providers: [HTTP_PROVIDERS],
config: {} // http://ionicframework.com/docs/v2/api/config/Config/
})
export class MyApp {
static get parameters() {
return [[Platform]];
}
constructor(platform) {
this.rootPage = TabsPage;
platform.ready().then(() => {
});
}
}
Please try this
export class Page1 {
static get parameters(){
return [Http];
}
constructor(http) {
this.http = http;
this.mget = this.http.get("https://httpbin.org/ip")
.subscribe(data => {
var alert = Alert.create({
title: "Your IP Address",
subTitle: data.json().origin,
buttons: ["close"]
});
this.nav.present(alert);
}, error => {
console.log(JSON.stringify(error.json()));
});
}
}
I would recommend you to write the get request inside a separate service and inject it in your page.
Also have a look at this - http://tphangout.com/?p=113
Detailed and simple instructions are given there for making a simple GET request from an Ionic 2 app.
I believe you need to
import { HTTP_PROVIDERS } from 'angular2/http';
in your app.js instead of HTTP_BINDINGS and change providers: [HTTP_BINDINGS] to providers: [HTTP_PROVIDERS]
See Angular2 docs