I came accross this issue about a simple password change form, in which I want to make sure that the new password is different from the old
I've tried implementing it directly following the documentation :
import { ref, withParams } from 'vuelidate/lib/validators'
export const differsFrom = equalTo => withParams(
{type: 'differsFrom', eq: equalTo},
function (value, parentVm) {
return value !== ref(equalTo, this, parentVm)
}
)
Now, importing the proper items from vuelidate is not as easy as the documentation states it.
Use not:
import { sameAs, not } from 'vuelidate/lib/validators'
export default {
data () {
return {
password: '',
oldPassword: ''
}
},
validations: {
password: {
not(sameAs('oldPassword'))
}
}
}
Hints:
withParams is not a function there, have to import it like this:
import { withParams } from 'vuelidate/lib/params'
I could not find how to import ref properly... It kept saying that it was not a function.
Now, an implementation of a not validator works just as well:
import { withParams } from 'vuelidate/lib/params'
export const not = validator => {
return withParams({type: 'not'}, (...args) => !validator(...args))
}
Related
In almost all guides, tutorial, posts, etc that I have seen on vuex module registration, if the module is registered by the component the createNamespacedHelpers are imported and defined prior to the export default component statement, e.g.:
import {createNamespacedHelpers} from 'vuex'
const {mapState} = createNamespacedHelpers('mymod')
import module from '#/store/modules/mymod'
export default {
beforeCreated() {
this.$store.registerModule('mymod', module)
}
}
this works as expected, but what if we want the module to have a unique or user defined namespace?
import {createNamespacedHelpers} from 'vuex'
import module from '#/store/modules/mymod'
export default {
props: { namespace: 'mymod' },
beforeCreated() {
const ns = this.$options.propData.namespace
this.$store.registerModule(ns, module)
const {mapState} = createNamespacedHelpers(ns)
this.$options.computed = {
...mapState(['testVar'])
}
}
}
I thought this would work, but it doesnt.
Why is something like this needed?
because
export default {
...
computed: {
...mapState(this.namespace, ['testVar']),
...
},
...
}
doesnt work
This style of work around by utilising beforeCreate to access the variables you want should work, I did this from the props passed into your component instance:
import { createNamespacedHelpers } from "vuex";
import module from '#/store/modules/mymod';
export default {
name: "someComponent",
props: ['namespace'],
beforeCreate() {
let namespace = this.$options.propsData.namespace;
const { mapActions, mapState } = createNamespacedHelpers(namespace);
// register your module first
this.$store.registerModule(namespace, module);
// now that createNamespacedHelpers can use props we can now use neater mapping
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
}),
// because we use spread operator above we can still add component specifics
aFunctionComputed(){ return this.name + "functions";},
anArrowComputed: () => `${this.name}arrows`,
};
// set up your method bindings via the $options variable
this.$options.methods = {
...mapActions(["initialiseModuleData"])
};
},
created() {
// call your actions passing your payloads in the first param if you need
this.initialiseModuleData({ id: 123, name: "Tom" });
}
}
I personally use a helper function in the module I'm importing to get a namespace, so if I hadmy module storing projects and passed a projectId of 123 to my component/page using router and/or props it would look like this:
import projectModule from '#/store/project.module';
export default{
props['projectId'], // eg. 123
...
beforeCreate() {
// dynamic namespace built using whatever module you want:
let namespace = projectModule.buildNamespace(this.$options.propsData.projectId); // 'project:123'
// ... everything else as above
}
}
Hope you find this useful.
All posted answers are just workarounds leading to a code that feels verbose and way away from standard code people are used to when dealing with stores.
So I just wanted to let everyone know that brophdawg11 (one of the commenters on the issue #863) created (and open sourced) set of mapInstanceXXX helpers aiming to solve this issue.
There is also series of 3 blog posts explaining reasons behind. Good read...
I found this from veux github issue, it seems to meet your needs
https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
{
props: ['namespace'],
computed: mapState({
state (state) {
return state[this.namespace]
},
someGetter (state, getters) {
return getters[this.namespace + '/someGetter']
}
}),
methods: {
...mapActions({
someAction (dispatch, payload) {
return dispatch(this.namespace + '/someAction', payload)
}
}),
...mapMutations({
someMutation (commit, payload) {
return commit(this.namespace + '/someMutation', payload)
})
})
}
}
... or maybe we don't need mapXXX helpers,
mentioned by this comment https://github.com/vuejs/vuex/issues/863#issuecomment-439039257
computed: {
state () {
return this.$store.state[this.namespace]
},
someGetter () {
return this.$store.getters[this.namespace + '/someGetter']
}
},
I am trying to test a ngrx effects in Angular 6 project, I always get error:
Expected $[0].notification.kind = 'C' to equal 'N'.
Expected $[0].notification.hasValue = false to equal true.
I tried this post https://brianflove.com/2018-06-28/ngrx-testing-effects and the one in the ngrx doc. Is there any requirements to make test on effects with ngrx 6 ? The error is not meaningful enough for me. Maybe someone have a complete example about how to do ?
Here's my effect:
initData$: Observable<Action> = this.actions$.pipe(
ofType(INIT_DATA_ACTION),
switchMap((data: any) => {
return this.store.pipe(select(getAppDataResolved)).take(1).switchMap((resolved: any) => {
if (!resolved) {
return this.dataService.getInitData(this.loginService.user.id).switchMap((response: any) => {
return Observable.from([
new ItemsInitDataAction(response.userItems),
new InitDataResolvedAction(),
]);
});
} else {
return Observable.from([
new InitDataResolvedAction(),
]);
}
});
}),
);
and my karma test:
it('should be created', () => {
expect(effects).toBeTruthy(); // got success
});
it('basic test', () => { // got error
const action = new appAction.InitDataAction();
const outcome = new appAction.InitDataResolvedAction();
actions.stream = hot('a', { a: action });
const expected = hot('a', { b: outcome });
expect(effects.initData$).toBeObservable(expected);
});
});
Thanks in advance for helping ;-)
I think you need to insert a mock for the Selector, the next line in the console should be describe something about missing selector data.
let store: Store<any>
class MockStore {
select(){}
}
TestBed.configureTestingModule({
providers: [
{
provide: Store,
useClass: MockStore
}
]
});
store = TestBed.get(Store);
And in test suite you can use Spy to give you any slice of store that you want:
spyOn(store, 'select').and.returnValue(of(initialState));
There is a Typo in expected. It should be 'b' instead of 'a'
const expected = hot('b', { b: outcome });
I havent been able to get a testing working with marbles yet.
Im using Nrwl nx, so my effects test looks like this:
import { TestBed } from '#angular/core/testing';
import { Subject, ReplaySubject } from 'rxjs';
import { EffectsModule } from '#ngrx/effects';
import { StoreModule } from '#ngrx/store';
import { provideMockActions } from '#ngrx/effects/testing';
import { NxModule } from '#nrwl/nx';
import { DataPersistence } from '#nrwl/nx';
import { ChangePasswordEffects } from './change-password.effects';
import { ChangePassword, ChangePasswordSuccessful } from './change-password.actions';
import { HttpClientTestingModule } from '#angular/common/http/testing';
describe('ChangePasswordEffects', () => {
let actions: Subject<any>;
let effects: ChangePasswordEffects;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NxModule.forRoot(), StoreModule.forRoot({}), EffectsModule.forRoot([]), HttpClientTestingModule],
providers: [ChangePasswordEffects, DataPersistence, provideMockActions(() => actions)]
});
effects = TestBed.get(ChangePasswordEffects);
});
describe('loadChangePassword$', () => {
it('should work', () => {
actions = new ReplaySubject(1);
actions.next(ChangePassword);
effects.loadChangePassword$.subscribe(result => {
expect(result).toEqual(ChangePasswordSuccessful);
});
});
});
});
And my code looks like this:
import { PasswordChangeError } from './../../models/password-change-error';
import { Injectable } from '#angular/core';
import { Effect, Actions } from '#ngrx/effects';
import { DataPersistence } from '#nrwl/nx';
import { ChangePasswordPartialState } from './change-password.reducer';
import {
ChangePassword,
ChangePasswordSuccessful,
ChangePasswordError,
ChangePasswordActionTypes
} from './change-password.actions';
import { ChangePasswordService } from '../../services/change-password/change-password.service';
import { map } from 'rxjs/operators';
#Injectable()
export class ChangePasswordEffects {
#Effect() loadChangePassword$ = this.dataPersistence.fetch(ChangePasswordActionTypes.ChangePassword, {
run: (action: ChangePassword, state: ChangePasswordPartialState) => {
return this.passwordService
.changePassword(action.newPassword, action.userId)
.pipe(map(res => new ChangePasswordSuccessful(res)));
},
onError: (action: ChangePassword, error: PasswordChangeError) => {
return new ChangePasswordError(error);
}
});
constructor(
private actions$: Actions,
private dataPersistence: DataPersistence<ChangePasswordPartialState>,
private passwordService: ChangePasswordService
) {}
}
I'm trying to make test development with Angular 6 and GraphQl but we really don't know how to do as the best way possible. I have tried to find something on the internet that explains this, but nothing really good has been found.
I'll post my case here looking for someone who could help me to do, or someone who could tell me any tutorial/web to find more and good information.
The problem
I want to test an Auth. I have an auth.service.js and the respective spec.js. You can see it below:
AUTH_SERVICE_TS
import { Injectable } from '#angular/core';
import { Store } from '#ngrx/store';
import * as UserActions from './../../store/user/actions';
import gql from 'graphql-tag';
import { Apollo } from 'apollo-angular';
import { Router } from '#angular/router';
#Injectable({
providedIn: 'root'
})
export class AuthService {
user;
constructor(private store: Store<any>, private apollo: Apollo, private router: Router) {
this.store.select('state').subscribe(state => this.user = state);
}
/**
* Function that check the email and password for login
* #param email
* #param password
*/
login(email: string, password: string) {
this.apollo.mutate({
mutation: this.loginRequestGql(),
variables: {
email: email,
password: password
}
}).subscribe(value => {
const data = value.data.login;
this.saveUserData(data);
this.router.navigate(['/app']);
});
}
/**
* Function that save user data in the store and in the session storage
* #param data
*/
saveUserData(data) {
this.store.dispatch(new UserActions.Login({token: data.token}));
this.setSessionStorage(this.user);
}
/**
* Function that remove user info in the store
*/
logout() {
this.store.dispatch(new UserActions.Logout());
this.setSessionStorage(this.user);
}
/**
* Function that create the request with Graphql sintax
*/
loginRequestGql() {
return gql`
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
}
}
`;
}
/**
* Function that save in the session storage the data parameter
* #param data
*/
setSessionStorage(data) {
sessionStorage.setItem('session', JSON.stringify(data));
}
}
AUTH_SERVICE_SPEC_TS
import { TestBed, inject } from '#angular/core/testing';
import { ApolloTestingController, ApolloTestingModule } from "apollo-angular/testing";
import { RouterTestingModule } from '#angular/router/testing';
import { AuthService } from './auth.service';
import { Store, StoreModule } from '#ngrx/store';
import { reducer } from '../../store/user/reducer';
describe('AuthService', () => {
let backend: ApolloTestingController;
let authService: AuthService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ RouterTestingModule, ApolloTestingModule, StoreModule.forRoot({ state: reducer }) ],
providers: [ AuthService,
{ provide: ApolloTestingModule, useClass: ApolloTestingModule }
]
});
});
beforeEach(() => {
backend = TestBed.get(ApolloTestingController);
authService = TestBed.get(AuthService);
});
it('should be created', inject([AuthService], (service: AuthService) => {
expect(service).toBeTruthy();
}));
it('should test login', (done) => {
const email = 'diego#mail.com';
const password = '123456';
// const a = authService.login(email, password);
// expect(a).toBe(TEST_RESPONSE['data'].login.token);
// authService.login(email, password);
// backend.expectOne(authService.loginRequestGql).flush(TEST_RESPONSE);
});
});
const TEST_RESPONSE: Object = {
"data": {
"login": {
"token": "eyJ0eXAiOiJKLCJhbGciOiJSUzI1NiIsImp0aSI6IjZjZDBjMDMXX0.as7-r_nlYfJ2w3CfOqwtLcTlBg5LrwFcm_ZXZ_GzCl5Qq0GS92r5tqGJtFzRfG02PPoLZ8uwsbgLj-5v2pYBXHjBLZvbjnW_zgXRLoDEcrBDpfPAoVH85ca_hb_xVaIgEUGumUPfn2IOx0Ce8fLlqtWGqoWtWzcCE
}
};
Thanks in advance to the community!! Hope you can help me!!
PD: If you need more information, just request and i'll give.
In one of recent versions of apollo-angular we released a testing utilities. Testing technique is pretty much similar to how you test HttpClient in Angular.
To learn more about how to test components and services that uses Apollo, please read the official documentation about it.
https://www.apollographql.com/docs/angular/guides/testing.html
It seems we cannot use expectOne with a simple DocumentNode parameter when doing a mutation.
So instead of:
backend.expectOne(authService.loginRequestGql).flush(TEST_RESPONSE);
we must pass to expectOne a function which asserts the operation's query definition is the expected one:
backend.expectOne((operation) => {
expect(operation.query.definitions).toEqual(mutation.definitions);
return true;
})
.flush(TEST_RESPONSE);
I have a question regarding the TypeAhead as I didn't want to pollute the git space backlog.
I setup the typeahead to work with my own observable based on the async demo (I'm pulling the google prediction data) and the typehead kind of works, but has the refresh (or change detection) issue where I'm typing the correct address but the highlighted results are always one or two letters 'behind' in terms of highlighting, or the results are missing as the search might have been narrowed down. The component does update if I for example press the key left or right, which tells me there must be some detection issue.
If there any way I could force it do detect changes? I've tried to run the change detector right after the asyncaction but that didn't help. Thanks heaps
Here is the stackblitz code
https://stackblitz.com/edit/angular-ufgm4x
To see what I struggle to understand where is the delay, try to follow these steps:
quickly type in e.g. '30 Manni'
wait for the response, wait a little, let's say 3 sec
then press 'k' and wait and don't interact with the app...wait and then only after couple of seconds the component updates (match highlight). Or press 'k', wait a little and interact with the app and you will see the highlight kicks-in.
It appears that this is not the google place lookup response time as they are quite good. There must be something else.
This odd behavior is especially noticeable with the search delay
[typeaheadWaitMs]="1000"
export class TypeaheadComponent {
asyncSelected: string;
typeaheadLoading: boolean;
typeaheadNoResults: boolean;
dataSource: Observable<any>;
constructor(private geocoder: GeocodeService,
private chd: ChangeDetectorRef,
private zone: NgZone) {
this.dataSource = Observable.create((observer: any) => {
// Runs on every search
observer.next(this.asyncSelected);
}).mergeMap((token: string) => this.geocoder.getSuggestions(token)).do(() => {
setTimeout(() => {
this.chd.detectChanges(); // --> Doesn't do anything
}, 200);
});
}
changeTypeaheadLoading(e: boolean): void {
this.typeaheadLoading = e;
}
typeaheadOnSelect(e: TypeaheadMatch): void {
console.log('Selected value: ', e.value);
}
}
public getSuggestions(keyword: string): Observable<object> {
if (typeof google === 'undefined') {
return new Observable<object>();
}
const autocompleter = new google.maps.places.AutocompleteService();
return new Observable<object>((observer) => {
// Prepare the callback for the autocomplete
const onPredictionsReady = (predictions: any[]) => {
observer.next(predictions || []);
observer.complete();
};
// do the search
autocompleter.getPlacePredictions({ input: keyword }, onPredictionsReady);
});
}
I had the same problem, here's how I solved it:
My service:
import {Injectable} from '#angular/core';
import {MapsAPILoader} from '#agm/core';
import {Observable} from 'rxjs/Rx';
import {} from 'googlemaps';
#Injectable()
export class GooglePlacesService {
googleAutocompleteService;
constructor(private mapsAPILoader: MapsAPILoader) {
this.mapsAPILoader.load().then(() => {
this.googleAutocompleteService = new google.maps.places.AutocompleteService();
});
}
getPredictions(inputText: string) {
const callback = this.googleAutocompleteService.getPlacePredictions.bind(this.googleAutocompleteService);
const observable = Observable.bindCallback(callback, (predictions, status) => {
if (status !== google.maps.places.PlacesServiceStatus.OK) {
return [];
} else {
return predictions;
}
});
return observable({
input: inputText
});
}
}
My component (ts):
import {Component, NgZone, OnInit} from '#angular/core';
import {} from 'googlemaps';
import {Observable} from 'rxjs/Observable';
import AutocompletePrediction = google.maps.places.AutocompletePrediction;
import {GooglePlacesService} from '../../api/google-places.service';
#Component({
selector: 'search-location-input',
templateUrl: './search-location-input.component.html',
styleUrls: ['./search-location-input.component.css']
})
export class SearchLocationInputComponent implements OnInit {
inputText = '';
predictions: Observable<AutocompletePrediction[]>;
constructor(private googlePlacesService: GooglePlacesService,
private ngZone: NgZone) {
}
ngOnInit() {
this.predictions = Observable.create((observer: any) => {
this.googlePlacesService.getPredictions(this.inputText)
.subscribe((result: any) => {
this.ngZone.run(() => observer.next(result));
});
});
}
}
My component (html):
<input [(ngModel)]="inputText"
[typeahead]="predictions"
typeaheadOptionField="description"
[typeaheadWaitMs]="200"
type="text">
I’m new to using Vue. I’m trying to wrap my head around plugins. What i’m stuck on is using a component and its method that I add to my plugin:
Component: Rest.vue
...
export default {
name: 'rest',
data () {
return {
}
},
methods: {
gplFetch: function(query){
...
return ...;
}
}
}
...
Plugin: global-plugin
import Rest from ‘#/components/Rest.vue’
export default {
install(Vue, options) {
Vue.component(Rest.name, Rest)
Vue.mixin({
created() {
console.log('rest created');
}
})
Vue.prototype.$gplFetch = function(query){
return <Access Rest component>.gplFetch(query);
}
}
}
Using in main.js
import GlobalPlugin from '#/plugins/global-plugin.js'
Vue.use(GlobalPlugin);
What i’m stuck on is how to access gplFetch in the code above:
return <Access Rest component>.gplFetch(query);
In order to make the code work the return should be
return Rest.methods.gplFetch(query);
But I would suggest taking a different approach and creating a module that contains the gplFetch function (or perhaps an API module) and importing that method into both your plugin and the Rest.vue component.
gplFetch.js
export function gplFetch(query){
// do something with query
}
Rest.vue
import {gplFetch} from "./gplFetch.js"
export default {
name: 'rest',
data () {
return {
}
},
methods: {
gplFetch
}
}
global-plugin.js
import {gplFetch} from "./gplFetch.js"
export default {
install(Vue, options) {
Vue.component(Rest.name, Rest)
Vue.mixin({
created() {
console.log('rest created');
}
})
Vue.prototype.$gplFetch = function(query){
return gplFetch(query);
}
}
}
This of course, all assumes that gplFetch doesn't rely on any data in the Rest.vue instance, because if it does it won't work from your plugin in the first place.