I have a form with mat-autocomplete and mat-chip-list components. It works fine with static data that i have in an array. But when i try to load data remotly from a service i have an error :
this.carCtrl.valueChanges.startWith is not a function
Here's my code in the Controller :
import { startWith } from 'rxjs/operators/startWith';
import { map} from 'rxjs/operators/map';
cars: Car[] = [{ id: 1, brand: "honda"}, { id: 2, brand: "toyota"}];
constructor(public dialogRef: MatDialogRef<DialogForm>, #Inject(MAT_DIALOG_DATA) public data: any, public formBuilder: FormBuilder, private carService: CarService) {
// It gives me the error mentionned above
this.carCtrl.valueChanges
.startWith(null)
.subscribe(val => {
if (val && typeof val !== 'object') {
this.carService.getCars(this.search).subscribe(cars=> {
this.filteredCars = cars;
});
}
});
// this Works with a static array
this.filteredCars = this.carCtrl.valueChanges.pipe(
startWith(null),
map((car: Car | null) => this.cars.slice()));
And my template :
<input placeholder="Add cars..."
#carInput
[formControl]="carCtrl"
[matAutocomplete]="autoCars"
[matChipInputFor]="chipEbomList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event,'car')">
Related
I want to create a select box for the location field, in which if one types any letter should call API and fetch location details in the dropdown
I tried the below code but didn't work
<select class="js-data-example-ajax form-control" id="FilterLocation"></select>
#Html.Hidden("FilterLocation", new { id = "locationId" })
In the script written below code
function setLocation() {
$('.js-data-example-ajax').select2({
ajax: {
type: 'PUT',
url: function (params) {
return '/api/GoogleCustomSearch/getLocation?matchingName=' + params.term
},
delay: 250,
data: function (params) {
var query = {
}
// Query paramters will be ?search=[term]&page=[page]
return query;
},
processResults: function (data) {
data = JSON.parse(data);
let results = []
if (data.location !== null) {
data.location.forEach((e) => {
results.push({
id: e,
text: e
})
})
}
return {
results: results
};
}
},
placeholder: "Search"
})
$('.js-data-example-ajax').on('change',function(e){
var selVal = $('#FilterLocation').val()
$('#locationId').val(selVal)
//getZipCodeForDynamic(selVal)
})
var $newOption = $("<option selected='selected'></option>")
$("#FilterLocation").append($newOption).trigger('change');
}
Dropdown options are not getting with above code.
Here is a working demo for select2:
View:
<select id="mySelect2" class="js-data-example-ajax" style="width:200px"></select>
js:
$('#mySelect2').select2({
ajax: {
type: 'PUT',
url: "GetSelect2Data",
delay: 250,
data: function(params) {
var query = {
search: params.term,
}
// Query parameters will be ?search=[term]
return query;
},
processResults: function(data) {
//data = JSON.parse(data);
let results = []
if (data.location !== null) {
data.location.forEach((e) => {
results.push({
id: e,
text: e
})
})
}
return {
results: results
};
}
},
placeholder: "Search"
});
model:
public class Select2Model {
public List<string> Location { get; set; }
}
action:
[HttpPut]
public ActionResult GetSelect2Data(string Search)
{
return Json(new Select2Model() { Location = new List<string> { "a"+Search,"b" + Search, "c" + Search } });
}
result:
EXPECTING:
Be able to get user info with id equal to my id only (which is saved in JWT token).
CURRENT RESULT:
I am able to get info about all users with some id.
Used Nest Js docs while creating this solution.
Do appreciate your help.
/casl-ability.factory.ts
type Subjects = InferSubjects<typeof User | typeof Role | 'User'> | 'all';
export type AppAbility = Ability<[Action, Subjects]>;
export class CaslAbilityFactory {
createForUser(userDataFromJWT: JwtAccessTokenInput) {
const { can, cannot, build } = new AbilityBuilder<
Ability<[Action, Subjects]>
>(Ability as AbilityClass<AppAbility>);
// TESTING THIS CASE
can(Action.Read, User, {
id: userDataFromJWT.sub,
});
return build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
}
private hasRole(roles: unknown[], role: UserRoles): boolean {
return roles.includes(role);
}
}
/getUser.policyHandler.ts
export class GetUserPolicyHandler implements IPolicyHandler {
handle(ability: AppAbility) {
return ability.can(Action.Read, User);
}
}
/types.ts
export enum Action {
Manage = 'manage',
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}
export interface IPolicyHandler {
handle(ability: AppAbility): boolean;
}
type PolicyHandlerCallback = (ability: AppAbility) => boolean;
export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;
/policies.guard.ts
#Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const policyHandlers =
this.reflector.get<PolicyHandler[]>(
CHECK_POLICIES_KEY,
context.getHandler(),
) || [];
const ctx = GqlExecutionContext.create(context);
const { user }: { user: JwtAccessTokenInput } = ctx.getContext().req;
const ability = this.caslAbilityFactory.createForUser(user);
return policyHandlers.every((handler) =>
this.execPolicyHandler(handler, ability),
);
}
private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) {
if (typeof handler === 'function') {
return handler(ability);
}
return handler.handle(ability);
}
}
user.resolver.ts
#Resolver(() => User)
export class UserResolver {
constructor(private readonly userService: UserService) {}
#Query(() => User, { name: 'user' })
#UseGuards(PoliciesGuard)
#CheckPolicies(new GetUserPolicyHandler())
#UseInterceptors(UserNotExistsByIDInterceptor)
async findOne(#Args('id', { type: () => Int }) id: number): Promise<User> {
return await this.userService.findOne(id);
}
}
possible duplicate of NestJS + CASL + Mongoose: CASL cannot infer subject type from Mongoose Schema
if you're using mongoose you need to inject the model to allow InferSubjects to retrieve the type thus allowing you to use filters and fields.
I am working on a laravel 8 project, am using Vue and Inertia when i try to update an image it clears the rest of the data in the form. But if i update the the form without updating the image it works fine. I can create a new record with an image i find that only when i try to update the image that's when this happens
Input field looks like this
<input type="file" class="h-full w-full" #input="form.image = $event.target.files[0]">
And this is my vue
import AppLayout from "#/Layouts/AppLayout";
export default {
name: "Edit",
props:{
errors: Object,
product: Object,
productTypes: Object,
},
components: {AppLayout},
data(){
return {
form: this.$inertia.form({
name: this.product.name,
product_type_id: this.product.product_type_id,
url: this.product.url,
status_id: this.product.status_id,
image: this.product.image
})
}
},
methods:{
update() {
this.form.put(this.route('product.update', this.product.id))
},
}
}
And below is my update method in the controller
public function update(UpdateProductRequest $request, Product $product)
{
if ($request->hasFile('image')){
$file = $request->file('image');
$image = Image::make($file);
$path = storage_path().DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'public'.DIRECTORY_SEPARATOR.'products'.DIRECTORY_SEPARATOR;
$file_name = time().'_'.$file->getClientOriginalName();
if ($image->height() == $image->width() && $image->height() > 1080) {
$image->resize(1080, 1080);
$image->save($path.$file_name);
} else {
$blur = Image::make($file)->fit(1080, 1080)->blur(20);
$image->resize(1080, 1080, function ($c) {
$c->aspectRatio();
$c->upsize();
});
$blur->insert($image, 'center');
$blur->save($path.$file_name);
}
$product->image = 'products'.DIRECTORY_SEPARATOR.$file_name;
}
$product->name = $request->name;
$product->product_type_id = $request->product_type_id;
$product->url = $request->url;
$product->status_id = $request->status_id;
$product->save();
return Redirect::route('product.index');
}
I even tried this
import {useForm} from "#inertiajs/inertia-vue3";
export default {
name: "Create",
props:{
errors: Object,
product: Object,
productTypes: Object,
},
setup (props) {
const form = useForm({
name: props.product.name,
image: props.product.image,
url: props.product.url,
product_type_id: props.product.product_type_id,
status_id: props.product.status_id
})
function submit() {
form.patch('/product/'+props.product.id)
}
return { form, submit }
}
}
when i submit the form it fails validation saying name, product_type_id, url, and status_id is required even if i didn't change them i just changed the image
I've got a component that I want to update when a person's name changes by emitting an event. My problem is the code doesn't compile because of an error. This is my code
ApplicationFormComponent
#Output() nameChange = new EventEmitter();
closeAccordion(isComplete: string, accordionToClose: string, accordion: NgbAccordion) {
if (accordionToClose === 'personal-details-panel') {
this.applicationStatusFlags.personalDetailsStatus = (isComplete === 'true');
this.nameChange.emit({ personId: this.personId });
}
}
ApplicationFormComponent.html
<name-display
[personId]="personId"
[placeHolderText]="'Hello'"
(nameChange)="update($event)">
</name-display>
NameDisplayComponent
import { Component, Input, OnChanges, SimpleChanges } from '#angular/core';
import { PersonService } from "../../../service/person.service";
#Component({
selector: 'name-display',
templateUrl: './NameDisplay.component.html',
providers: [PersonService]
})
export class NameDisplayComponent implements OnChanges {
constructor(private readonly personService: PersonService) { }
#Input() personId;
#Input() placeHolderText: string = "";
forename: string = "";
ngOnChanges(changes: SimpleChanges): void {
if (changes["personId"]) {
this.personService.getPersonDetails(this.personId).subscribe((res: IPersonDetails) => {
this.forename = res.forenames;
});
}
};
update(personId: number) {
alert("update name");
this.personService.getPersonDetails(personId).subscribe((res: IPersonDetails) => {
this.forename = res.forenames;
});
}
}
My problem is basically when I use angular cli with the command ng server --aot, it doesn't compile because of this error:
ERROR in src\app\component\ApplicationForm\ApplicationForm.component.html(42,9): : Property 'update' does not exist on type 'ApplicationFormComponent'.
I've written a similar component that uses an event emitter which doesn't have this problem, so I'm stuck with how to fix the error.
Any ideas?
It is because you are passing $event to method.
(nameChange)="update($event)"
But it accepts number.
update(personId: number) {
alert("update name");
}
Please change the method as below.
update(event:any) {
const personId = event as number
alert("update name");
}
I am learning GraphQL so I built a little project. Let's say I have 2 models, User and Comment.
const Comment = Model.define('Comment', {
content: {
type: DataType.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
});
const User = Model.define('User', {
name: {
type: DataType.STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
phone: DataType.STRING,
picture: DataType.STRING,
});
The relations are one-to-many, where a user can have many comments.
I have built the schema like this:
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: {
type: GraphQLString
},
name: {
type: GraphQLString
},
phone: {
type: GraphQLString
},
comments: {
type: new GraphQLList(CommentType),
resolve: user => user.getComments()
}
})
});
And the query:
const user = {
type: UserType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(_, {id}) => User.findById(id)
};
Executing the query for a user and his comments is done with 1 request, like so:
{
User(id:"1"){
Comments{
content
}
}
}
As I understand, the client will get the results using 1 query, this is the benefit using GraphQL. But the server will execute 2 queries, one for the user and another one for his comments.
My question is, what are the best practices for building the GraphQL schema and types and combining join between tables, so that the server could also execute the query with 1 request?
The concept you are refering to is called batching. There are several libraries out there that offer this. For example:
Dataloader: generic utility maintained by Facebook that provides "a consistent API over various backends and reduce requests to those backends via batching and caching"
join-monster: "A GraphQL-to-SQL query execution layer for batch data fetching."
To anyone using .NET and the GraphQL for .NET package, I have made an extension method that converts the GraphQL Query into Entity Framework Includes.
public static class ResolveFieldContextExtensions
{
public static string GetIncludeString(this ResolveFieldContext<object> source)
{
return string.Join(',', GetIncludePaths(source.FieldAst));
}
private static IEnumerable<Field> GetChildren(IHaveSelectionSet root)
{
return root.SelectionSet.Selections.Cast<Field>()
.Where(x => x.SelectionSet.Selections.Any());
}
private static IEnumerable<string> GetIncludePaths(IHaveSelectionSet root)
{
var q = new Queue<Tuple<string, Field>>();
foreach (var child in GetChildren(root))
q.Enqueue(new Tuple<string, Field>(child.Name.ToPascalCase(), child));
while (q.Any())
{
var node = q.Dequeue();
var children = GetChildren(node.Item2).ToList();
if (children.Any())
{
foreach (var child in children)
q.Enqueue(new Tuple<string, Field>
(node.Item1 + "." + child.Name.ToPascalCase(), child));
}
else
{
yield return node.Item1;
}
}}}
Lets say we have the following query:
query {
getHistory {
id
product {
id
category {
id
subCategory {
id
}
subAnything {
id
}
}
}
}
}
We can create a variable in "resolve" method of the field:
var include = context.GetIncludeString();
which generates the following string:
"Product.Category.SubCategory,Product.Category.SubAnything"
and pass it to Entity Framework:
public Task<TEntity> Get(TKey id, string include)
{
var query = Context.Set<TEntity>();
if (!string.IsNullOrEmpty(include))
{
query = include.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Aggregate(query, (q, p) => q.Include(p));
}
return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
}