Implement Sorting Functionality in Angular Using FormArray

Hello, I am a beginner in Angular and need some help with implementing sorting functionality. I have an input class called Foo which contains a list of books with properties like Id, Title, and Description. These books are displayed in a table where users can add, edit, or delete them. The feature to add and delete books is working correctly.

However, when I tried to add sorting using MatSort and Sort from Angular itself, it didn't work as expected. I'm not sure what I'm doing wrong. Should I switch to using MatTable instead of looping through a form array to achieve sorting? And if yes, how can I implement this with inputs instead of displaying data variables like {{element.title}}?

I appreciate any assistance on this matter.

Thank you!

@Input() foo: Foo;
@ViewChild(MatSort, {static: true}) sort: MatSort;
bookForm: FormArray;
orderForm: FormGroup;
bookList !: Book[];
bookSorted : Book[];
initForm() {
  this.orderForm= this._formBuilder.group( {
    customerForm: this._formBuilder.array( [] ),
    bookForm: this._formBuilder.array( [] )
  } );

  this.addedBooks()
  this.bookList = this.foo.Books;
}

addedBooks() {
  this.bookForm= this.orderForm.get( 'bookForm' ) as FormArray;
  this.bookForm.clear();
  let _bookForm = this.foo.books?.map( _book => this.addBook( _book ) );
  _bookForm?.forEach( _addBook => this.bookForm.push( _addBook ) );

}

addBook( _book) {
  return this._formBuilder.group( {
    title: new FormControl( _book?.title),
    description: new FormControl( _book?.description ),
    id: new FormControl( _book?.id ?? Guid.EMPTY ),
  } );
}

get bookFormControls {
  return ( this.orderForm.get( 'bookForm' ) as FormArray ).controls;
}

sortBook(sort: Sort) {
  const data = this.bookList.slice();
  if (!sort.active || sort.direction == '') {
    this.bookSorted = data;
    return;
  }

  this.bookSorted = data.sort((a, b) => {
    let isAsc = sort.direction == 'asc';
    switch (sort.active) {
      case 'title': return this.compare(a.title, b.title, isAsc);
      case 'description': return this.compare(+a.description, +b.description, isAsc);
      default: return 0;
    }
  });
}

compare(a, b, isAsc) {
  return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}

removeBooksAt( index ) {
  this.dialogName = "Book"
  this.modalRef = this.dialog.open( this.deleteBook, {
    width: '600px',
  } );
  this.modalRef.afterClosed().subscribe( res => {
    if ( res ) this.bookForm.removeAt( index );
  } );

}

addNewBook() {
  let formValue = this.orderForm.controls['bookForm'] as FormArray;
  formValue.status == 'VALID' ? this.createBooksForm() : this.showToast();
}

createBooksForm(data?: any) {
  this.booksForm = this.orderForm.get( 'booksForm ' ) as FormArray;
  this.booksForm .push( this.addBooksControls(data) );
}

addBooksControls(data?: any): FormGroup {
  return this._formBuilder.group( {
    title: [data?.title ??'', Validators.required],
    description: [data?.description ??'', Validators.required],
    id: [data?.id ??Guid.EMPTY]
  } );
}

HTML

<!--Mat Sort Test-->
<fieldset>
  <div>
    <legend>Books</legend>
    <table matSort (matSortChange)="sortBook($event)" class="card-table">
      <thead class="primary-color">
      <tr>
        <th mat-sort-header="title">
          Book Title
        </th>
        <th mat-sort-header="description">
          Description
        </th>
        <th class="colums-name">
          Actions
        </th>
      </tr>
      </thead>
      <tbody>
      <tr class="margin-1" formArrayName="bookForm"
          *ngFor="let group of bookFormControls; let _i = index;">
        <td [formGroupName]="_i">
          <input type="text" formControlName="title" class="margin-1 readonly" placeholder="Add title">
        </td>
        <td [formGroupName]="_i">
          <input type="text" formControlName="description" class="margin-1 readonly"
                 placeholder="Add description">
          <input type="hidden" formControlName="id">
        </td>
        <td style="text-align: center;">
          <i (click)="removeBooksAt(_i, 'Title')" class="fa fa-trash margin-right-mini"
             style="color:darkgrey; font-size: xx-large;;" aria-hidden="true"></i>
        </td>
      </tr>
      </tbody>
    </table>
  </div>
</fieldset>

Answer №1

Here's a summary of the changes that have been made:

  1. I added [formGroup] at the top of the table, but you may not need it if you already have it elsewhere in your code.

  2. Moved the formArrayName to the tbody element to be the parent of the *ngFor

  3. Transferred the [formGroupName] to the *ngFor line to ensure it is the parent of the form elements

  4. Ensure that you've imported MatSortModule into the child component

  5. Make sure you included provideAnimations() in the providers array of bootstrapApplication

  6. Note that using both Books and books interchangeably is incorrect; I standardized them all as books

  7. The main issue was sorting the data instead of the form controls; make sure to use the same formGroup array for sorting as well.

Changes to the sort function:

  Updated sortBook(sort: Sort) {
    // Function content here
  }

Please review the full code below and check for any doubts or missing points:

FULL CODE:

CHILD TS

 Updated TypeScript code

CHILD HTML

 Updated HTML code with necessary changes

PARENT

 Updated Parent Component 

Access the updated Stackblitz Demo here

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating callback functions that vary based on input variables

Consider the following code snippet, which may seem somewhat contrived: arbitraryFunction( // Input that is dynamically generated [ returnValue("key1", "a"), returnValue("key2", 1), returnValue ...

Display the customer's name using the Mat-Select display object property by fetching it from the previously selected or saved value

Having some difficulties with mat-select and options in my Angular 6 project. Here is the scenario I am facing: I would like to create a dropdown list of organizations where I can select one and save the complete organization object, not just its name. ...

Using Angular to pass a class as a parameter in an HTTP GET request

I am currently working with a class that looks like this: export class CodeTable { public tableId: number; public connectionTable: number; public connectionCode: number; public code: number; ...

Using AngularJS with the Chosen Plugin to pre-select a value in a dropdown menu

After following the guidance from this URL, I successfully incorporated the chosen plugin into Angular.js. Now that I can retrieve the value, my next objective is to have the selected value be pre-selected in the chosen dropdown menu. If anyone has a sim ...

Showing ng-attributes in haml partials in Rails using jQuery ajax and $q

As I work on developing an Angular Frontend for an existing Rails Application, I have come across the challenge of integrating $q in my current setup. While I understand that transitioning to a REST API served directly to ngResource would be ideal, the com ...

What steps should I follow to have my edit form component extract values from HTML in Angular?

I created a detailed listing page that includes a picture URL, title, phone number, description, and price. When the user clicks on the Edit button, it redirects them to a page with input forms for each of these fields. The form automatically populates the ...

Retrieve the generic type parameter of an interface implementation

I am attempting to extract a type parameter from an interface in order to use it as a parameter for a generic function. In my particular scenario, I have the following generic types: interface ITranslatable<T, K extends keyof T> { translations: IT ...

Is it possible to convert a type to a JSON file programmatically?

Recently, I have been tasked with implementing configuration files for my system, one for each environment. However, when it came time to use the config, I realized that it was not typed in an easy way. To solve this issue, I created an index file that imp ...

Error: The function 'useThemeProps' is not available for import from the module '@material-ui/core/styles'

I encountered an error message saying 'Attempted import error: 'useThemeProps' is not exported from '@material-ui/core/styles'. Everything was working fine on the website yesterday, but after I deleted package-lock.json and node_mo ...

Error occurs when trying to open a modal popup within a component due to changes in expression

In my application, I am facing an issue where I have a parent component passing HTML content to a child common component using @ViewChild(). However, when the Child component loads up a popup, the console throws the following error: "ExpressionChangedAft ...

Executing the command "node-gyp rebuild" will produce a build log in the file "build_log.txt", and any errors will be redirected

<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="90e5e7e3d0a9bea1a4bea0">[email protected]</a> install /var/www/html/my-app/node_modules/uws node-gyp rebuild > build_log.txt 2>&1 || exit 0 Error: There ...

Navigating from a table row to an individual item within Angular using routing

I am currently working on a page that displays a table of Listings (also known as Products). The task at hand is to include an Edit Listing button in each row which will redirect to a new page for editing a single Listing (Product). Essentially, the flow ...

Issue with Angular Router: unable to retrieve route parameters in child paths

Within our main app.component routing, we have the following setup: { path: 'store', loadChildren: './store/store.module#StoreModule', canActivate: [LoginGuard] }, Then, in the module, our routes are defined as follows: const routes: ...

`The font appears extremely thin when viewed on Safari and iOS devices.`

I'm struggling to resolve the issue with super-thin fonts on Safari/iOS despite trying all recommended solutions. Here is the comparison: https://i.stack.imgur.com/5DVHz.png The _document.js file: import Document, { Html, Head, Main, NextScript } fr ...

Changing HTML elements dynamically within an ng-repeat using AngularJS directives

I have devised an angular directive where I execute an ng-repeat. The fundamental purpose of this directive is to interchange itself with a distinct directive that depends on a value forwarded into the original directive: <content-type-directive type=" ...

Description: TypeScript type that derives from the third constructor parameter of a generic function

How can I determine the type of constructor props for a generic type? Take a look at this example. type PatchableProps<T> = T extends { [k: string | number]: any } ? { [Key in keyof T]: PatchableProps<T[Key]> } : T | Patch export class ...

Leveraging foreign key attributes within Angular templates

My technology stack includes Django for the backend with Django Rest Framework and Angular for the frontend. Within the backend, I have defined 2 models: class Post(models.Model): category = models.ForeignKey(Category, on_delete=models.SET_NULL, null= ...

Can an Expression be incorporated into the ng-model Directive?

I am facing an issue with my loop: <div ng-repeat="measure in CompleteDataSetViewModel.TargetMeasures"> </div> Within this loop, I have an input field: <input ng-model="CompleteDataSetViewModel.AnnualRecord.Target[measure.Name]_Original" ...

The server has access to an environment variable that is not available on the client, despite being properly prefixed

In my project, I have a file named .env.local that contains three variables: NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY=pk_test_<get-your-own> MAGIC_SECRET_KEY=sk_test_<get-your-own> TOKEN_SECRET=some-secret These variables are printed out in the file ...

How can I bring in a dynamic MaterialUI theme object from another file?

Could anyone provide guidance on the correct syntax for importing a dynamic theme object from a separate file? I am attempting to retrieve the system theme based on a media query and then dynamically set the theme. Everything works as expected when all t ...