At the moment I have these codes:
import 'package:chopper/chopper.dart';
part 'first_api.chopper.dart';
#ChopperApi(baseUrl: '')
abstract class FirstApi extends ChopperService {
#Get(path: '/api/v1/mobile/first/')
Future<Response> getFirsts();
static FirstApi create() {
final client = ChopperClient(
baseUrl: 'http://10.0.2.2:8081',
services: [
_$FirstApi(),
],
converter: JsonConverter(),
);
return _$FirstApi(client);
}
}
and
import 'package:chopper/chopper.dart';
part 'second_api.chopper.dart';
#ChopperApi(baseUrl: '')
abstract class SecondApi extends ChopperService {
#Get(path: '/api/v1/mobile/second/')
Future<Response> getSeconds();
static SecondApi create() {
final client = ChopperClient(
baseUrl: 'http://10.0.2.2:8081',
services: [
_$SecondApi(),
],
converter: JsonConverter(),
);
return _$SecondApi(client);
}
}
Then I call it like this:
FirstApi.getInstance().getFirsts();
SecondApi.getInstance().getSeconds();
But I see that it accepts more than one services...
services: [
_$SecondApi(),
],
Can I lower the amount of code somehow? (If not, I will have 5 more classes just like these...)
If it is possible, how and how to use it?
Edit:
it works like this:
You create services, an example:
File: test_api.dart
import 'package:chopper/chopper.dart';
part 'test_api.chopper.dart';
#ChopperApi(baseUrl: '/')
abstract class TestApi extends ChopperService {
#Get(path: '/modified/{modified}')
Future<Response> getData(#Path('modified') String modified);
static TestApi create([ChopperClient? client]) => _$TestApi(client);
}
Create the client:
class CustomChopperClient {
static Future<ChopperClient> createChopperClient() {
final client = ChopperClient(
baseUrl: "http://example.com",
services: [
TestApi.create(),
TestApiTwo.create(),
],
interceptors: [],
converter: JsonConverter(),
);
return client;
}
}
Then you can use the servies like this:
ChopperClient chopperClient = CustomChopperClient.createChopperClient();
TestApi testApi = chopperClient.getService<TestApi>();
TestApiTwo testApiTwo = chopperClient.getService<TestApiTwo>();
// now you can call the getData() that you created in the TestApi service
testApi.getData();
Related
I am using the Fastify Adapter in my NestJS application and would like to add some logic to do JWKS validation, similar to the passport example on the Auth0 website.
// src/authz/jwt.strategy.ts
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';
import * as dotenv from 'dotenv';
dotenv.config();
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${process.env.AUTH0_ISSUER_URL}.well-known/jwks.json`,
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: process.env.AUTH0_AUDIENCE,
issuer: `${process.env.AUTH0_ISSUER_URL}`,
algorithms: ['RS256'],
});
}
validate(payload: unknown): unknown {
return payload;
}
}
It is my understanding that Passport only works with Express and will not work with Fastify. Does anyone know how to do something like this with Fastify and NestJS ?
I didn't manage to find a library like passport to do the JWKS validation with fastify. I decided to write my own validation using the jsonwebtoken and the #types/jsonwebtoken libraries.
Below is a sample of my solution for anybody else that is interested :)
File structure is as follows:
src
|__ auth
|__jwks
|_ jwks.client.ts
|_ jwks.service.ts
|_ jwt-auth.guard.ts
|_ jwt-auth.module.ts
|__ caching
|_ redis-cache.module.ts
|__ models
|__ json-web-key.model.ts
|__ jwks-response.model.ts
|__ my.controller.ts
|__ app.module.ts
models for the jwks response
// src/models/jwks-response.model.ts
import { JsonWebKey } from "src/models/json-web-key.model";
export class JwksResponse {
keys: Array<JsonWebKey>
}
// src/models/json-web-key.model.ts
export class JsonWebKey {
kty: string;
kid: string;
use: string;
x5t: string;
x5c: Array<string>;
n?: string;
e?: string;
x?: string;
y?: string;
crv?: string;
}
client to call the jwks endpoint and process the response
//src/auth/jwks/jwks.client.ts
import { HttpException, Injectable, Logger } from "#nestjs/common";
import { ConfigService} from "#nestjs/config";
import { HttpService } from "#nestjs/axios";
import { map, lastValueFrom } from "rxjs";
import { JwksResponse } from "src/models/jwks-response.model";
import { JsonWebKey } from "src/models/json-web-key.model";
#Injectable()
export class JwksClient {
private readonly logger: Logger = new Logger(JwksClient.name);
private readonly JWKS_URL: string = this.configService.get<string>('services.jwks.url');
private readonly TIMEOUT: number = parseInt(this.configService.get<string>('services.timeout'));
constructor(private configService: ConfigService, private httpService: HttpService){}
async getJsonWebKeySet(): Promise<Array<JsonWebKey>> {
this.logger.log(`Attempting to retrieve json web keys from Jwks endpoint`);
const config = {
timeout: this.TIMEOUT,
};
let response: JwksResponse = null;
try {
response = await lastValueFrom(this.httpService.get(this.JWKS_URL, config)
.pipe(
map((response) => {
return response.data;
},
),
),
);
} catch(e) {
this.logger.error(`An error occurred invoking Jwks endpoint to retrieve public keys`);
this.logger.error(e.stack);
throw new HttpException(e.message, e.response.status);
}
if (!response || !response.keys || response.keys.length == 0) {
this.logger.error('No json web keys were returned from Jwks endpoint')
return [];
}
return response.keys;
}
}
service containing logic to call jwks endpoint and verify the jwt token with the public key.
The JWT token will consist of a header, payload and a signature.
The header should also have a kid field that will match the kid of one of the json web keys, so that you know which one to verify your token with.
The x5c array contains a certificate chain and the first element of this array will always contain the certificate that you use to get the public key from to verify the token.
Note: I had to wrap the certificate in with \n-----BEGIN CERTIFICATE-----\n${key.x5c[0]}\n-----END CERTIFICATE----- to be able to create the public key but you may not have to do this in your implementation.
You will also need to add logic to verify the claims for your JWT.
I have also cached a valid JWT for a period of time to ensure that the verification is not required each time as this would have performance implications, the key for this cache uses the auth token to ensure that it is unique.
import { HttpException, HttpStatus, Injectable, CACHE_MANAGER, Logger, Inject } from "#nestjs/common";
import { ConfigService} from "#nestjs/config";
import { IncomingHttpHeaders } from "http";
import { JwksClient } from "src/auth/jwks/jwks.client";
import { JsonWebKey } from "src/models/json-web-key.model";
import { JwtPayload } from 'jsonwebtoken';
import * as jwt from 'jsonwebtoken';
import * as crypto from "crypto";
import { Cache } from 'cache-manager';
#Injectable()
export class JwksService {
private readonly logger: Logger = new Logger(JwksService.name);
private readonly CACHE_KEY: string = this.configService.get<string>('caches.jwks.key');
private readonly CACHE_TTL: number = parseInt(this.configService.get<string>('caches.jwks.ttl'));
constructor(private configService: ConfigService, private readonly jwksClient: JwksClient, #Inject(CACHE_MANAGER) private cacheManager: Cache){}
async verify(request: any): Promise<boolean> {
let token: string = this.getAuthorizationTokenFromHeader(request.headers);
const jwksKey = `${this.CACHE_KEY}:${token}`
const cachedVerificationResult: boolean = await this.getCachedVerificationResult(jwksKey);
if (cachedVerificationResult) {
this.logger.debug("Found cached verification result");
return cachedVerificationResult;
}
if (!this.hasTokenWithValidClaims(token)) {
this.logger.error("Token with invalid claims was provided")
return false;
}
// Get all web keys from JWKS endpoint
let jsonWebKeys: Array<JsonWebKey> = await this.jwksClient.getJsonWebKeySet();
// Find the public key with matching kid
let publicKey: string | Buffer = this.findPublicKey(token, jsonWebKeys);
if (!publicKey) {
this.logger.error("No public key was found for the bearer token provided")
return false;
}
try {
jwt.verify(token, publicKey, { algorithms: ['Put algorithm here e.g. HS256, ES256 etc'] });
} catch(e) {
this.logger.error("An error occurred verifying the bearer token with the associated public key");
this.logger.error(e.stack);
throw new HttpException(e.message, HttpStatus.FORBIDDEN);
}
// Cache Jwks validation result
this.cacheManager.set(jwksKey, true, { ttl: this.CACHE_TTL });
this.logger.debug("Successfully verified bearer token with the associated public key")
return true;
}
private hasTokenWithValidClaims(token: string) {
var { header, payload, signature } = jwt.decode(token, { complete: true });
// TODO: Add validation for claims
return true;
}
private findPublicKey(token: string, jsonWebKeys: Array<JsonWebKey>): string | Buffer {
var { header } = jwt.decode(token, { complete: true });
let key = null;
for (var jsonWebKey of jsonWebKeys) {
if (jsonWebKey.kid === header.kid) {
this.logger.debug(`Found json web key for kid ${header.kid}`);
key = jsonWebKey;
break;
}
}
if (!key) {
return null;
}
// Exctact x509 certificate from the certificate chain
const x509Certificate = `\n-----BEGIN CERTIFICATE-----\n${key.x5c[0]}\n-----END CERTIFICATE-----`;
// Create the public key from the x509 certificate
return crypto.createPublicKey(x509Certificate).export({type:'spki', format:'pem'})
}
private getAuthorizationTokenFromHeader(headers: IncomingHttpHeaders): string {
if(!headers || !headers.authorization) {
throw new HttpException("Authorization header is missing", HttpStatus.BAD_REQUEST);
}
let token: string = headers.authorization;
if (token.startsWith("Bearer ")) {
token = headers.authorization.split(" ")[1].trim();
}
return token;
}
private async getCachedVerificationResult(jwksKey: string): Promise<boolean> {
const response: boolean = await this.cacheManager.get(jwksKey);
if(response && response === true) {
return response;
}
return null;
}
}
guard to verify the JWT
// src/auth/jwks/jwt-auth.guard.ts
import { Injectable, CanActivate, ExecutionContext, Logger } from '#nestjs/common';
import { JwksService } from 'src/auth/jwks/jwks.service';
#Injectable()
export class JwtAuthGuard implements CanActivate {
private readonly logger: Logger = new Logger(JwtAuthGuard.name);
constructor(private jwksService: JwksService){}
async canActivate(
context: ExecutionContext,
): Promise<boolean> {
const request = context.switchToHttp().getRequest();
return await this.jwksService.verify(request);
}
}
module containing config for jwks
// src/auth/jwks/jwt-auth.model.ts
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { HttpModule } from '#nestjs/axios';
import configuration from '../../../config/configuration';
import { JwksClient } from 'src/auth/jwks/jwks.client';
import { JwksService } from 'src/auth/jwks/jwks.service';
#Module({
imports: [
ConfigModule.forRoot({ load: [configuration] }),
HttpModule
],
providers: [
JwksClient,
JwksService,
],
exports: [JwksService, JwksClient],
})
export class JwtAuthModule {}
redis caching module containing config for redis cache
// src/caching/redis-cache.module.ts
import { CacheModule, Module } from '#nestjs/common';
import { ConfigModule, ConfigService } from '#nestjs/config';
import configuration from '../../config/configuration';
import { RedisClientOptions } from 'redis';
import * as redisStore from 'cache-manager-redis-store';
#Module({
imports: [
ConfigModule.forRoot({ load: [configuration] }),
CacheModule.registerAsync<RedisClientOptions>({
isGlobal: true,
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
store: redisStore,
host: process.env.REDIS_URL,
port: configService.get<number>('redis.port'),
password: configService.get<string>('redis.password'),
tls: configService.get<boolean>('redis.tls')
}),
inject: [ConfigService],
})
],
controllers: [],
providers: []
})
export class RedisCacheModule {}
controller that uses the JwtAuthGuard
// src/my.controller.ts
import { Controller, Get, Param, Logger } from '#nestjs/common';
#Controller()
#UseGuards(JwtAuthGuard)
export class MyController {
private readonly logger: Logger = new Logger(MyController.name);
#Get('/:id')
async getCustomerDetails(#Headers() headers, #Param('id') id: string): Promise<Customer> {
this.logger.log(`Accepted incoming request with id: ${id}`);
// Do some processing ....
return customer;
}
}
module containing configuration for whole app
// src/app.module.ts
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { HttpModule } from '#nestjs/axios';
import configuration from '../config/configuration';
import { JwtAuthModule } from 'src/auth/jwks/jwt-auth.module';
import { RedisCacheModule } from 'src/caching/redis-cache.module';
#Module({
imports: [
ConfigModule.forRoot({ load: [configuration] }),
HttpModule,
JwtAuthModule,
RedisCacheModule
],
controllers: [MyController],
providers: []
})
export class AppModule {}
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) {
// ...
}
We are using NestJS with mongoose and want to seed mongoDB.
Wondering what is the proper way to seed the database, and use the db schemas already defined to ensure the data seeded is valid and properly maintained.
Seeding at the module level (just before the definition of the Module) feels hacky and ends in threadpool being destroyed, and therefore all following mongo operations fail
I've done using the nestjs-command library like that.
1. Install the library:
https://www.npmjs.com/package/nestjs-command
2. Then I've created a command to seed my userService like:
src/modules/user/seeds/user.seed.ts
import { Command, Positional } from 'nestjs-command';
import { Injectable } from '#nestjs/common';
import { UserService } from '../../../shared/services/user.service';
#Injectable()
export class UserSeed {
constructor(
private readonly userService: UserService,
) { }
#Command({ command: 'create:user', describe: 'create a user', autoExit: true })
async create() {
const user = await this.userService.create({
firstName: 'First name',
lastName: 'Last name',
mobile: 999999999,
email: 'test#test.com',
password: 'foo_b#r',
});
console.log(user);
}
}
3. Add that seed command into your module. I've created a SeedsModule in a shared folder to add more seeds in future
src/shared/seeds.module.ts
import { Module } from '#nestjs/common';
import { CommandModule } from 'nestjs-command';
import { UserSeed } from '../modules/user/seeds/user.seed';
import { SharedModule } from './shared.module';
#Module({
imports: [CommandModule, SharedModule],
providers: [UserSeed],
exports: [UserSeed],
})
export class SeedsModule {}
Btw I'm importing my userService into my SharedModule
4. Add the SeedsModule into your AppModule
On your AppModule usually at src/app.module.ts add the SeedsModule into imports
Final
If you followed the steps in the nestjs-command repo you should be able to run
npx nestjs-command create:user
That will bootstrap a new application and run that command and then seed to your mongo/mongoose
Hope that help others too.
actually you can do it easily with onModuleInit(), here i'm using Mongoose ORM. This all done with zero dependencies, hope it helps
import { Injectable, OnModuleInit } from '#nestjs/common';
import { UserRepository } from './repositories/user.repository';
#Injectable()
export class UserService implements OnModuleInit {
constructor(private readonly userRepository: UserRepository) {}
// onModuleInit() is executed before the app bootstraped
async onModuleInit() {
try {
const res = await this.userRepository.findAll(); // this method returns user data exist in database (if any)
// checks if any user data exist
if (res['data'] == 0) {
const newUser = {
name: 'yourname',
email: 'youremail#gmail.com',
username: 'yourusername',
};
const user = await this.userRepository.create(newUser); // this method creates new user in database
console.log(user);
}
} catch (error) {
throw error;
}
}
// your other methods
}
For my case, I needed to insert seed during the tests, the best I could find is to create a seed service, imported and used only during tests.
Here is my base class using the schema model, all is needed is to extend and pass the model.
// # base.seed.service.ts
import { Model, Document } from 'mongoose';
import { forceArray, toJson } from 'src/utils/code';
export abstract class BaseSeedService<D extends Document> {
constructor(protected entityModel: Model<D>) {}
async insert<T = any>(data: T | T[]): Promise<any[]> {
const docs = await this.entityModel.insertMany(forceArray(data));
return toJson(docs);
}
}
// # utils
const toJson = (arg: any) => JSON.parse(JSON.stringify(arg));
function forceArray<T = any>(instance: T | T[]): T[] {
if (instance instanceof Array) return instance;
return [instance];
}
// # dummy.seed.service.ts
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { DummyDocument } from './dummy.schema';
#Injectable()
export class DummySeedService extends BaseSeedService<DummyDocument> {
constructor(
#InjectModel(Dummy.name)
protected model: Model<DummyDocument>,
) {
super(model);
}
}
Then inside the tests
describe('Dymmy Seeds', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [DummySeedService],
imports: [
MongooseModule.forRoot(__connect_to_your_mongodb_test_db__),
MongooseModule.forFeature([
{
name: Dummy.name,
schema: DummySchema,
},
]),
],
}).compile();
const seeder = module.get<DummySeedService>(DummySeedService);
const initData = [__seed_data_here__];
const entities: Dummy[] = await seeder.insert(initData);
expect(entities.length > 0).toBeTruthy();
});
});
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!
Im trying to write some Unit Test for a service like:
export class Service1 {
...
public constructor(service2: Service2, service3: Service3) {}
...
}
where Service2 contains, in turn, Service3:
export class Service2 {
...
public constructor(service3: Service3, stringa: string) {}
...
}
i starting test
in a really awful way, like:
it("check XXX", () => {
let service3: Service3= new Service3();
let service2: service2= new service2(service3, "stringa");
let service1: Service1 = new Service1(service2, service3);
expect(X).toEqual(X);
});
but now, i'm using beforeEachProviders and what happen with this:
beforeEachProviders(() => {
return [
provide(Service2, { useclass: MockService2}),
provide(Service3, { useclass: MockService3}),
provide('stringa', { useValue: "stringa"}),
Service1
];
});
it('check XXX ', inject(
[Service2, Service3], (service2: Service2, service3: Service3) => {
expect(true).toEqual(true);
}));
is:
Failed: No provider for String! (Service2-> String).
Could someboby help me ?
Thanks in Advance.
If you want to inject the string value from dependency injection, you need to use the #Inject decorator:
export class Service2 {
...
public constructor(service3: Service3, #Inject('stringa') stringa: string) {}
...
}
As a matter of fact, injection relies on parameter types by default if you don't use the #Inject decorator.