6

I went though this, and understand that after declaring a data-bound input property, Angular should automatically update the input value. In the component I created, it doesn't seems like that.

When I click on a item on the grid on the parent it shows me the details properly. When I click on another item after that, it doesn't update the child component. I have put a console.log to monitor the selected record. It keeps changing according to the user selection.

Can you please have a look and help me to understand where the issue is?

lists.component.html [Parent]

<div class="container-fluid">
  <div class="row mt-2 mb-2">
    <div class="col-8">
      <app-list-item-add *ngIf="showAddnewScreen" (notifyParentOnUpdate)='onAddItem($event)'></app-list-item-add>
      <app-list-item-view *ngIf="showViewScreen" [studentObject]="selectedstudent" (notifyParentOnUpdate)='onViewItem($event)'></app-list-item-view>
    </div>
  </div>
</div>

lists.component.ts [Parent]

import { Component, OnInit, ViewChild } from '@angular/core';
import { studentsService, student } from '../services/students.service';
import { Router, ActivatedRoute } from '@angular/router';
import { GridComponent, ToolbarItems, SortEventArgs, RowSelectEventArgs, SelectionSettingsModel } from '@syncfusion/ej2-ng-grids';
import { ClickEventArgs } from '@syncfusion/ej2-ng-navigations';
import * as moment from 'moment';
import { Internationalization } from '@syncfusion/ej2-base';

@Component({
  selector: 'app-lists',
  templateUrl: './lists.component.html',
  styleUrls: ['./lists.component.scss']
})
export class ListsComponent implements OnInit {

  constructor(public router: Router, private route: ActivatedRoute, private studentsService: studentsService) { }
  selectedstudent: student = null;
  students: student[] = new Array<student>();
  intl: Internationalization = new Internationalization();
  showAddnewScreen = false;
  showViewScreen = false;

  // Syncfusion GRID settings for the students grid.
  // Documentation: https://ej2.syncfusion.com/16.1.32/angular/documentation/grid/getting-started.html
  studentsGridId = 'studentsGrid';
  @ViewChild('studentsGrid')
  studentsGrid: GridComponent;
  toolbar: ToolbarItems[];

  // https://ej2.syncfusion.com/16.1.32/angular/documentation/grid/api-filterSettings.html
  studentsFilteringSettings = {
  };

  // https://ej2.syncfusion.com/16.1.32/angular/documentation/grid/api-pageSettings.html
  studentsPageSettings = {
    currentPage: 1,
    enableQueryString: true,
    pageSizes: [10, 25, 50, 100],
    pageSize: 10
  };

  // https://ej2.syncfusion.com/16.1.32/angular/documentation/grid/api-selectionSettings.html
  studentsSelectionOptions: SelectionSettingsModel;

  studentsToolbarClick(args: ClickEventArgs) {
    // handles multiple grids on the page by prepending the Grid ID to the _eventname
    // E.g.
    // if (args.item.id == studentsGrid_excelexport)....
    if (args.item.id === (this.studentsGridId + '_excelexport')) {
      this.studentsGrid.excelExport();
    }
    if (args.item.id === (this.studentsGridId + '_pdfexport')) {
      this.studentsGrid.pdfExport();
    }
  }

  studentsRowSelected(args: RowSelectEventArgs) {
    const selectedrowindex: number[] = this.studentsGrid.getSelectedRowIndexes();  // Get the selected row indexes.
    console.log(selectedrowindex);
    const selectedRecords: student[] = this.studentsGrid.getSelectedRecords() as student[];  // Get the selected records.
    const selectedRecord = selectedRecords[0];
    if (selectedRecord) {
    }
  }

  gridActionHandler(args: SortEventArgs) {
    console.log(args.requestType + ' ' + args.type);
  }

  ngOnInit() {
    this.toolbar = ['Print', 'Search', 'ExcelExport', 'PdfExport'];
    this.studentsSelectionOptions = {
      type: 'Single',
      mode: 'Row'
    };

    this.studentsService.getstudents(1000).subscribe((students) => {
      this.students = students;
      this.students.sort(this.sortBystudentNumber);
      this.studentsGrid.dataSource = this.students;
    });

    // Listen for changes to list items
    this.studentsService.studentAdded$.subscribe(student => {
      // convert the students date strings into dates
      student.createdOn = moment(student.createdOn).toDate();
      student.modifiedOn = moment(student.modifiedOn).toDate();
      // Add the new student to the list
      this.students.push(student);
      // resort the grid data
      this.students.sort(this.sortBystudentNumber);
      // refresh the grid
      this.studentsGrid.refresh();
    });
    this.studentsService.studentChanged$.subscribe(student => {
      // convert the students date strings into dates
      student.createdOn = moment(student.createdOn).toDate();
      student.modifiedOn = moment(student.modifiedOn).toDate();
      // Update the student in the list.
      this.students.splice(this.students.findIndex(s => s.id === student.id), 1, student);
      // resort the grid data
      this.students.sort(this.sortBystudentNumber);
      // refresh the grid
      this.studentsGrid.refresh();
    });
    this.studentsService.studentDeleted$.subscribe(id => {
      // Remove the student from the list
      this.students.splice(this.students.findIndex(s => s.id === id), 1);
      // resort the grid data
      this.students.sort(this.sortBystudentNumber);
      // refresh the grid
      this.studentsGrid.refresh();
    });

  }

  addNew() {
    this.showAddnewScreen = true;
    this.showViewScreen = false;
  }

  viewstudent(data: student) {
    console.log(data);
    this.selectedstudent = data;
    this.showViewScreen = true;
    this.showAddnewScreen = false;
  }

  onAddItem(student: student): void {
    this.showAddnewScreen = false;
  }

  onViewItem(command: string) {
    this.showViewScreen = false;

    if (command === 'cancel') {
    } else if (command === 'save') {
    } else if (command === 'delete') {
    }
  }

  sortBystudentNumber = (s1, s2) => s1.studentNumber - s2.studentNumber;
}

list-item-view.component.html [Child]

<div class="row">
  <div class="col-12">
    <section class="studentDetails">
      <app-section-title heading="student Details" level="4"></app-section-title>

      <form #studentForm="ngForm" class="pt-2">
        <div class="row">
          <div class="col-10">

            <div class="form-group">
              <div class="row">
                <div class="col-4">
                  <span>student number</span>
                </div>
                <div class="col-6">
                  <input type="text" class="form-control" aria-label="student number"
                         [(ngModel)]="student.studentNumber" name="student Number" />
                </div>
              </div>
            </div>

            <div class="form-group">
              <div class="row">
                <div class="col-4">
                  <span>student name</span>
                </div>
                <div class="col-6">
                  <input type="text" class="form-control" aria-label="student name"
                         [(ngModel)]="student.studentName" name="student Name" />
                </div>
              </div>
            </div>

            <div class="form-group">
              <div class="row">
                <div class="col-4">
                  <span>student description</span>
                </div>
                <div class="col-6">
                  <input type="text" class="form-control" aria-label="student description"
                         [(ngModel)]="student.description" name="Description" />
                </div>
              </div>
            </div>

            <div class="form-group">
              <div class="row">
                <div class="col-4">
                  <span>Created date</span>
                </div>
                <div class="col-6">   
                  <ejs-datepicker placeholder='Enter date' format="dd-MM-yyyyy" aria-label="Created date" [readonly]="true"
                                  [(ngModel)]="student.createdOn" name="Created On"></ejs-datepicker>
                  <label class="col-8 col-lg-9 col-form-label">({{student.createdOn | timeago}})</label>
                </div>
              </div>
            </div>

            <div class="form-group">
              <div class="row">
                <div class="col-4">
                  <span>Created by</span>
                </div>
                <div class="col-6">
                  <input type="text" class="form-control" aria-label="Created by" [readonly]="true"
                         [(ngModel)]="student.createdBy" name="Created By" />
                </div>
              </div>
            </div>

            <div class="form-group">
              <div class="row">
                <div class="col-4">
                  <span>Modified date</span>
                </div>
                <div class="col-6">
                  <ejs-datepicker placeholder='Enter date' format="dd-MM-yyyyy" aria-label="Modified date"
                                  [(ngModel)]="student.modifiedOn" name="Modified On"></ejs-datepicker>
                  <label *ngIf="student.modifiedOn" class="col-8 col-lg-9 col-form-label">({{student.modifiedOn | timeago}})</label>
                </div>
              </div>
            </div>

            <div class="form-group">
              <div class="row">
                <div class="col-4">
                  <span>Modified by</span>
                </div>
                <div class="col-6">
                  <input type="text" class="form-control" aria-label="Modified by"
                         [(ngModel)]="student.modifiedBy" name="Modified By" />
                </div>
              </div>
            </div>

          </div>
        </div>
      </form>
    </section>
  </div>
</div>

list-item-view.component.ts [Child]

import { Component, OnInit, ViewChild, EventEmitter, Output, Input } from '@angular/core';
import { studentsService, student } from '../services/students.service';
import { ActivatedRoute } from '@angular/router';
import { DatePicker } from '@syncfusion/ej2-calendars';
import * as moment from 'moment';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app-list-item-view',
  templateUrl: './list-item-view.component.html',
  styleUrls: ['./list-item-view.component.scss']
})

export class ListItemViewComponent implements OnInit {
  @Output() notifyParentOnUpdate: EventEmitter<any> = new EventEmitter<any>();
  @Input() studentObject: student;

  studentNumber: number;

  constructor(private route: ActivatedRoute, private studentsService: studentsService) { }

  @ViewChild(NgForm) studentForm: NgForm;

  public student = new student();

  ngOnInit() {
    this.student = this.studentObject;
  }

  save() {
    this.studentsService.updatestudent(this.student).subscribe(newstudent => {
      this.notifyParentOnUpdate.emit('save');
    });

  }

  delete() {
    this.studentsService.deletestudent(this.student.id).subscribe(newstudent => {
      this.notifyParentOnUpdate.emit('delete');
    });
  }

  editOnBlur() {
    this.notifyParentOnUpdate.emit('editOnBlur');
  }

  cancel() {
    this.notifyParentOnUpdate.emit('cancel');
  }
}

a_zatula
  • 196
  • 1
  • 15
Robert Nish
  • 81
  • 1
  • 1
  • 6
  • Apparently, your approach is right. Do you get any errors in the console? – John Gates Jan 15 '19 at 23:53
  • I think you need to change this line to two way databinding. `[schoolObject]="selectedSchool"` to `[(schoolObject)]="selectedSchool"` – Abedin.Zhuniqi Jan 15 '19 at 23:56
  • I am sorry. I just tried [(schoolObject)]="selectedSchool". It makes no difference. :-( – Robert Nish Jan 16 '19 at 00:27
  • I will update the question with a screen-shot of this page too. It will give you a clear picture – Robert Nish Jan 16 '19 at 00:28
  • 1
    Would you please use https://stackblitz.com to create a demo for us to re-produce your issues? thank you :) – Hearen Jan 16 '19 at 02:36
  • Sure, I will do it next time. The second answer worked for me. Thank you – Robert Nish Jan 16 '19 at 02:42
  • @RobertNish, Please remove the 'two-way-binding' tag, as it doesn't make any sense in this context. You only pass the data from parent to child and use an EventEmitter to notify the parent. That's it. – Kushan Randima Jan 16 '19 at 03:46
  • @JohnGates, If there are errors appear in the console, Robert must have seen it. Did you notice? He logs the school object on the console everytime it changes. Please see the the method - viewSchool() in lists.component.ts [Parent] – Kushan Randima Jan 16 '19 at 03:51

2 Answers2

6

When you write a child component in Angular 7, that updates its content whenever input changes. So, you can add all necessary computations to the ngOnChanges lifecycle hook. ngOnInit called once only.

Please add the following code to list.item-view.component.ts

  ngOnChanges() {
    this.school = this.schoolObject;
  }

Good Luck!

Kushan Randima
  • 1,738
  • 3
  • 25
  • 51
0

I think what's happening is: you are getting the school information on the 'schoolObject'. But what you are binding your values on the html file are 'school' object's properties. And on the current version of your ts file, I see that the school object is set only once on ngOnInit (disregarding the initialization).

I think you need to set the school object every time the schoolObject gets a new Input. Then it will run fine. This is what you can do:

schoolObject: School;
@Input()
set SchoolObject(schoolObj){
    this.schoolObject = schoolObj;
    this.school = schoolObj;
}

You will need to update the html to get the values using SchoolObject on your parent as:

<app-list-item-view *ngIf="showViewScreen" [SchoolObject]="selectedSchool" (notifyParentOnUpdate)='onViewItem($event)'></app-list-item-view>
alchi baucha
  • 772
  • 5
  • 15