MatPaginator gets undefined - angular-material2

I have replicated thsi case: Angular 5 Material Table not getting data from service
But when I try access any property from paginator I get undefined error from this objet.
Any ideas?
Thanks

I got the same issue. Placing mat-paginator tag outside *ngIf resolved my issue. Make sure it is available to component class without any conditions.

Some issues that may cause mat-paginator is undefined:
You forgot to import in app.module.ts import { MatPaginatorModule } from '#angular/material'; and then declare the import in the imports array inside ngModule.
#NgModule({ declarations: [ somestuff], imports: [ MatPaginatorModule]});
Import MatPaginator inside the component you are using:
import {MatPaginator, MatSort, MatTableDataSource} from '#angular/material';
set the MatDataSource to be none. (only need to do this if you are going to be getting async data e.g. from a server)
this.dataSource = new MatTableDataSource([]);
Make sure you set the length property of the mat-paginator to the length of the data rows returned.
Set the paginator inside NgAfterViewInit method or if that doesn't work try:
private paginator: MatPaginator;
private sort: MatSort;
#ViewChild(MatSort) set matSort(ms: MatSort) {
this.sort = ms;
this.setDataSourceAttributes();
}
#ViewChild(MatPaginator) set matPaginator(mp: MatPaginator) {
this.paginator = mp;
this.setDataSourceAttributes();
}
setDataSourceAttributes() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
if (this.paginator && this.sort) {
this.applyFilter('');
}
}

In some cases, the issue is related to conditional outer div. Ex:
<div *ngIf="condition">
...
<mat-paginator ....>
</div>
For a such scenario just replace *ngIf="condition" with [hidden]="!condition" and it'll work.
Please refer to https://github.com/angular/components/issues/10205 for more details

I had a similar problem and this is how I got it working:
My Initial Code Setup
component.html
<div class="chart-wrapper" *ngIf="viewType === 'chart'; else table;">
// Load Chart Here
</div>
<ng-template #table>
// Load Table Here
<mat-paginator
#paginator
[length]="tableDataSource.data ? tableDataSource.data.length : 0"
[pageSize]="pageSize"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)"
></mat-paginator>
</ng-template>
component.ts
columns: string[] = [];
tableDataSource: MatTableDataSource<any[]> = new MatTableDataSource([]);
#ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
pageIndex = 0;
pageSize = 10;
pageSizeOptions = [10, 15, 20];
ngOnInit() {
this.getTableData();
}
ngAfterViewInit() {
this.tableDataSource.paginator = this.paginator;
}
getTableData() {
// After getting data from API
this.tableDataSource.data = apiResponse;
}
The Solution
Put static: false while declaring Mat Paginator
#ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
and then set paginator onto tableDataSource after data gets loaded into it
this.tableDataSource.data = apiResponse;
this.tableDataSource.paginator = this.paginator;
Thanks to the solution by cisco336 on this thread
To my surprise nobody appraised that solution.

For me it was broken after angular upgrade from 7 to any version(although i checked in only angular 12).
In Angular 8 they introduced a parameter where we are supposed to give {static: true} if we need the value in ngOninit. From Angular 9 it was set to false by default if we are not mentioning it explicitly.
So changing code from this
#ViewChild(MatPaginator) paginator: MatPaginator;
To this
#ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
Fixed my issue.

"Inserting an *ngIf means that its contents cannot be queried until the view is initialized with change detection. This means the query for sort is empty at ngOnInit.
This can be resolved by changing the setup from ngOnInit to ngAfterViewInit. https://stackblitz.com/edit/angular-mewfek?file=src/app/app.component.ts "
reference
https://github.com/angular/components/issues/15966

In my case, paginator object was getting initiated in ngOnInit() and giving exception paginator is undefined.
Initial code i.e. not working code:
ngOnInit() {
this.dataSource = new StoreServiceDataSource<User>(this.userService, this.snackBar);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
After correction i.e. working code:
ngAfterViewInit() {
this.dataSource = new StoreServiceDataSource<User>(this.userService, this.snackBar);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
Just changed initializing life cycle hook. Earlier paginator was getting initialized in hgOnInit(), now it's initialized in ngAfterViewInit().

[length]="dataSource.filteredData.length" over "mat-paginator" worked for me.
<mat-paginator [pageSize]="10" [pageSizeOptions]="[10,25,50]" #paginator [length]="dataSource.filteredData.length"[showFirstLastButtons]="true"></mat-paginator>

MatPaginator being undefined most likely means you do not have the module imported. Sometimes angular mat does not explicitly tell you these things that you are missing. But always check the API tab under their documentation before using a component. At your module level, you should have the following in your app.module.ts file.
the import
import { MatButtonModule, MatTableModule, MatPaginatorModule, MatProgressSpinnerModule, MatTableDataSource } from '#angular/material';
Your component imported that used paginator of course
import { TableComponent } from './table/table.component';
Those modules imported in your imports array
imports: [
BrowserAnimationsModule,
NgbModule.forRoot(),
FormsModule,
RouterModule,
AppRoutingModule,
CommonModule,
MatButtonModule,
MatTableModule,
BrowserAnimationsModule,
MatPaginatorModule,
MatProgressSpinnerModule,
HttpClientModule
],
And those modules exported if necessary (different topic so I wont discuss here).
exports: [
MatButtonModule,
MatTableModule,
MatPaginatorModule
],
This is all happening in my App.Module
export class AppModule { }
This assuming you do bot have your project structured as feature modules. In that case you would really only need everything I talked about in the module in which your component lives. But in this case, where everything is under the app module, this works just fine.

<div class=" fixed-form-bottom" [hidden]="changeState">
<div class="team-footer">
<mat-paginator #paginator [pageSize]="pageSize" [showFirstLastButtons]="true" [length]="totalSize"
[pageIndex]="currentPage" (page)="pageEvent = handlePage($event)" [hidePageSize]="true">
</mat-paginator>
</div>
</div>
And in .ts file , you compare your condition and pass true/ false from there
if(this.totalSize>3){
this.changeState=true;
}
its worked for me

Check if you have *ngIf that wrap your mat-paginator or any top component, if you need hide you should use [hidden] attribute in your component

Another reason for the delay in pagination injection is one or more missing end tag such as within your html markup, which seems to impact the timing of the pagination markup DOM injection.
In this type of problem scenario a 2nd+ load of the table will show the pagination components.

There are two solutions to this problem
Solution 1:
If mat-paginator is inside *ngIf then either take it outside or use [hidden] attribute.
<div [hidden]="condition">
</div>
Solution 2:
Make sure you are not using static: true while initializing the paginator
#ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
change it to
#ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;

Related

How to update root level component data

I can't get how to initialize a root component data field into a blade view. In particular:
app.js:
import { createApp } from 'vue'
const app = createApp({
data() {
return {
count: 0
}
}
});
blade view:
...html...
<script>
// how can I update count with a blade var (i.e. {{ $count }}) created in my controller?
</script>
In Vue2 actually it was pretty easier, but I'm getting so crazy to get this out that I'm supposing this pattern is not good at all...
Thanks in advance for your kind support.

What is the correct way to bring data from the controller to the vue.js? Laravel-vue.js

I haven't really used this method before and I'm confused where I'm going wrong.
I set the data in the controller, I added the mycourses entry but it's not coming through, everything else is coming in fine
$data['user'] = Auth::user()->load('userType');
$data['courses'] = StudentSchedule::where('instructor_id',Auth::user()->id)
->with('course', 'package.packageCourses', 'user', 'location')->get();
$data['mycourses'] = Course::all();
$data['locations'] = Location::all();
return view('home', $data);
After this I go to the blade and add this
<script>
const user = {!! json_encode($user) !!};
const mycourses = {!! json_encode($mycourses) !!};
const courses = {!! json_encode($courses) !!};
const locations = {!! json_encode($locations) !!};
</script>
Then this goes to the .js, but whats happening there is that its calling the vue
In the vue file I add this to the data()
courses: [],
mycourses: [],
locations: [],
In the vue file I add this to the mounted
this.courses = courses;
this.mycourses = mycourses;
this.locations = locations;
And the mycourses is giving me this error
Error in mounted hook: "ReferenceError: mycourses is not defined"
ReferenceError: mycourses is not defined
I added mycourses after all the other ones were added before, I have everything the same as the courses one and I'm still getting this error. What am I missing?
I'm not sure if this will be helpful or not, meaning perhaps there is a reason this method won't work given your circumstances.
I've found this method of setting/passing data to be pretty helpful. It's less overhead to have to parse in your blade templates (or wherever needed).
Controller
public function myFunction() {
...
$user = Auth::user()->load('userType');
$courses = StudentSchedule::where('instructor_id', $user->id)->with('course', 'package.packageCourses', 'user', 'location')->get();
$mycourses = Course::all();
$locations = Location::all();
// Verify the data is correct.
// dd($courses);
return view('example', compact('user', 'courses', 'mycourses', 'locations');
}
You can verify each is returning the value needed by checking what it's returning using dd().
If you have a vue component you are passing data to that exists in your blade template, you could do something like this to pass the data in as props
example.blade.php
<p>Example content...</p>
<p>Hello, {{ $user->name }}</p>
<example-component :courses="courses" :mycourses="mycourses"></example-component>
<p>...</p>
Now we have passed courses and mycourses to the view component. You can access them there:
<script>
export default {
name: 'ExampleComponet',
data() {
return {
//
}
},
methods: {
//
},
props: {
courses: {
type: Object,
required: true
},
mycourses: {
type: Object,
required: true
},
},
}
</script>
You'll need to update the property type for each of the props you pass in. Perhaps courses isn't an object, but rather a string.
I'm not sure if I've exactly solved your issue, but hope this helps get you in the right direction.

How to use the pageContext in SPFx?

I am trying to get a value from the current page by using the pageContext but I am getting either undefined or 404.
This is the situation:
In the Site pages library there are several news pages. Each news page has some tags attached to them. This tags lives in a custom column in the Site Pages library.
There are news that have 1 tag and other several tags. It can be the situation where two or more news share the same tag(s).
The goal is when I open a news page the tags that are attached to that news are also visible.
Until now I am using #pnp/pnpjs and the code looks like this:
var result: any = await sp.web.lists.getByTitle("Site Pages")
.items.getById(15)
.select("Tags")
.get();
return await result.Tags;
And it is giving me 404 error
I also tried this one:
this.context.pageContext.list('Site Pages').listItem['Tags'].get().then((items: any[]) => {
console.log(items);
});
But it giving me Cannot read property 'list' of undefined
Du you have an idea how can get the value of the Tags column asociated with the current news?
Here is an update
Now I am getting the right tag. The question now is how to show it in the screen?
import * as React from 'react';
import styles from './ReadTags.module.scss';
import { IReadTagsProps } from './IReadTagsProps';
import { sp } from '#pnp/pnpjs';
export default class ReadTags extends React.Component<IReadTagsProps, {}> {
constructor(props: IReadTagsProps) {
super(props);
}
private async getTags() {
var id = this.props.context.pageContext.listItem.id;
var result: any = await sp.web.lists.getByTitle("Site Pages")
.items.getById(id)
.select("Tags")
.get();
return await result.Tags;
}
public render(): React.ReactElement<IReadTagsProps> {
console.log(this.getTags());
return (
<div className={ styles.readTags }>
<div className={ styles.container }>
<div className={ styles.row }>
<div className={ styles.column }>
</div>
</div>
</div>
</div>
);
}
}
Regards
Amerco
What you'll probably want to do is store your tags in the state of your component. Then you can show these (if the value from state is not empty) during your render. I can highly recommend working through the React tutorial to understand React lifecycle and state/props.
https://reactjs.org/tutorial/tutorial.html
https://reactjs.org/docs/state-and-lifecycle.html
Something with getting your data in componentDidMount, storing it in the state by using this.setState and then running through them in render with this.state.tags. It's more of a React question then a SPFx question :)
There's a ton of samples here with SPFx and React:
https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples

Aurelia - Passthrough Routes

Is it possible to render static HTML fragments without having an associated view-model in Aurelia? For instance, I have a typical header, body, footer layout. In body, I have the router view. There are a set of links in the footer such as FAQs upon clicking which I want to render a view in the body area.
When I try to define a route config for the faq route, the config is expecting one of You must specify a "moduleId:", "redirect:", "navigationStrategy:", or "viewPorts:".
The temporary work around that I have is to create a passthrough view model that doesn't do anything. This is resulting in a bunch of passthrough view model classes. I am sure I am doing something wrong.
I couldn't find any help online with this use-case. Any references will be highly appreciated.
It seems like you're looking for an inlineView("<your html here/>") type of functionality for routes so that navigating to the target route will directly render the HTML in the router-view element.
This is not directly possible with aurelia-router because without a ViewModel, no ActivationStrategy can be invoked. Aurelia-router wants to call canActivate, activate, canDeactivate, deactivate on something.
However, if you simply want to define markup programmatically, and you don't want to declare a ViewModel for each individual piece of markup, then that can be solved quite neatly with the compose element in combination with inlineViewStrategy.
With this approach, you only need one View/ViewModel pair which is responsible for retrieving the correct HTML based on the current route, and then render that HTML.
There are also other ways to do this, but AFAIK this approach involves the least amount of plumbing.
Of course you also need an object to store the HTML/route pairs in, and a service to store/retrieve those objects.
You can see a live working version here (including a few comments to clarify things):
https://gist.run/?id=8c7e02ce1ee0e25d966fea33b826fe10
app.js
import { inject } from "aurelia-framework";
import { Router } from "aurelia-router";
import { FaqService } from "./faq-service";
#inject(Router, FaqService)
export class App {
constructor(router, faqService) {
router.configure(config => {
config.map({ route: "", moduleId: "./empty" });
config.map({ route: "faq/:route", moduleId: "./faq-detail" });
});
this.router = router;
this.faqService = faqService;
}
openFaq(item) {
this.router.navigate(`faq/${item.route}`);
}
}
app.html
<template>
<router-view></router-view>
<ul>
<li repeat.for="item of faqService.faqItems" click.delegate="openFaq(item)">
${item.title}
</li>
</ul>
</template>
empty.js (just a convenience for default empty route):
import { inlineView } from "aurelia-framework";
#inlineView("<template>no content</template>")
export class Empty {}
faq-service.js
import { singleton } from "aurelia-framework";
class FaqItem {
constructor(route, title, markup) {
this.route = route;
this.title = title;
this.markup = markup;
}
}
#singleton(false)
export class FaqService {
constructor() {
this.faqItems = [
new FaqItem("question-1", "Question 1", "<h4>Question 1</h4><p>Answer 1</p>"),
new FaqItem("question-2", "Question 2", "<h4>Question 2</h4><p>Answer 2</p>"),
new FaqItem("question-3", "Question 3", "<h4>Question 3</h4><p>Answer 3</p>")
];
}
getByRoute(route) {
return this.faqItems.find(i => i.route === route);
}
}
faq-detail.js
import { inject, InlineViewStrategy } from "aurelia-framework";
import { FaqService } from "./faq-service";
#inject(FaqService)
export class FaqDetail {
constructor(service) {
this.service = service;
}
activate(param) {
let item = this.service.getByRoute(param.route);
this.viewStrategy = new InlineViewStrategy(`<template>${item.markup}</template>`)
}
}
faq-detail.html
<template>
<compose view.bind="viewStrategy"></compose>
</template>

Angular2 how to call a method only after subscribed data is completely bounded to a table using ng-for? [duplicate]

In Angular 1 I have written a custom directive ("repeater-ready") to use with ng-repeat to invoke a callback method when the iteration has been completed:
if ($scope.$last === true)
{
$timeout(() =>
{
$scope.$parent.$parent.$eval(someCallbackMethod);
});
}
Usage in markup:
<li ng-repeat="item in vm.Items track by item.Identifier"
repeater-ready="vm.CallThisWhenNgRepeatHasFinished()">
How can I achieve a similar functionality with ngFor in Angular 2?
You can use #ViewChildren for that purpose
#Component({
selector: 'my-app',
template: `
<ul *ngIf="!isHidden">
<li #allTheseThings *ngFor="let i of items; let last = last">{{i}}</li>
</ul>
<br>
<button (click)="items.push('another')">Add Another</button>
<button (click)="isHidden = !isHidden">{{isHidden ? 'Show' : 'Hide'}}</button>
`,
})
export class App {
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
#ViewChildren('allTheseThings') things: QueryList<any>;
ngAfterViewInit() {
this.things.changes.subscribe(t => {
this.ngForRendred();
})
}
ngForRendred() {
console.log('NgFor is Rendered');
}
}
origional Answer is here
https://stackoverflow.com/a/37088348/5700401
You can use something like this (ngFor local variables):
<li *ngFor="#item in Items; #last = last" [ready]="last ? false : true">
Then you can Intercept input property changes with a setter
#Input()
set ready(isReady: boolean) {
if (isReady) someCallbackMethod();
}
For me works in Angular2 using Typescript.
<li *ngFor="let item in Items; let last = last">
...
<span *ngIf="last">{{ngForCallback()}}</span>
</li>
Then you can handle using this function
public ngForCallback() {
...
}
The solution is quite trivial. If you need to know when ngFor completes printing all the DOM elements to the browser window, do the following:
1. Add a placeholder
Add a placeholder for the content being printed:
<div *ngIf="!contentPrinted">Rendering content...</div>
2. Add a container
Create a container with display: none for the content. When all items are printed, do display: block. contentPrinted is a component flag property, which defaults to false:
<ul [class.visible]="contentPrinted">
...items
</ul>
3. Create a callback method
Add onContentPrinted() to the component, which disables itself after ngFor completes:
onContentPrinted() {
this.contentPrinted = true;
this.changeDetector.detectChanges();
}
And don't forget to use ChangeDetectorRef to avoid ExpressionChangedAfterItHasBeenCheckedError.
4. Use ngFor last value
Declare last variable on ngFor. Use it inside li to run a method when this item is the last one:
<li *ngFor="let item of items; let last = last">
...
<ng-container *ngIf="last && !contentPrinted">
{{ onContentPrinted() }}
</ng-container>
<li>
Use contentPrinted component flag property to run onContentPrinted() only once.
Use ng-container to make no impact on the layout.
Instead of [ready], use [attr.ready] like below
<li *ngFor="#item in Items; #last = last" [attr.ready]="last ? false : true">
I found in RC3 the accepted answer doesn't work. However, I have found a way to deal with this. For me, I need to know when ngFor has finished to run the MDL componentHandler to upgrade the components.
First you will need a directive.
upgradeComponents.directive.ts
import { Directive, ElementRef, Input } from '#angular/core';
declare var componentHandler : any;
#Directive({ selector: '[upgrade-components]' })
export class UpgradeComponentsDirective{
#Input('upgrade-components')
set upgradeComponents(upgrade : boolean){
if(upgrade) componentHandler.upgradeAllRegistered();
}
}
Next import this into your component and add it to the directives
import {UpgradeComponentsDirective} from './upgradeComponents.directive';
#Component({
templateUrl: 'templates/mytemplate.html',
directives: [UpgradeComponentsDirective]
})
Now in the HTML set the "upgrade-components" attribute to true.
<div *ngFor='let item of items;let last=last' [upgrade-components]="last ? true : false">
When this attribute is set to true, it will run the method under the #Input() declaration. In my case it runs componentHandler.upgradeAllRegistered(). However, it could be used for anything of your choosing. By binding to the 'last' property of the ngFor statement, this will run when it is finished.
You will not need to use [attr.upgrade-components] even though this is not a native attribute due to it now being a bonafide directive.
I write a demo for this issue. The theory is based on the accepted answer but this answer is not complete because the li should be a custom component which can accept a ready input.
I write a complete demo for this issue.
Define a new component:
import {Component, Input, OnInit} from '#angular/core';
#Component({
selector: 'app-li-ready',
templateUrl: './li-ready.component.html',
styleUrls: ['./li-ready.component.css']
})
export class LiReadyComponent implements OnInit {
items: string[] = [];
#Input() item;
constructor() { }
ngOnInit(): void {
console.log('LiReadyComponent');
}
#Input()
set ready(isReady: boolean) {
if (isReady) {
console.log('===isReady!');
}
}
}
template
{{item}}
usage in the app component
<app-li-ready *ngFor="let item of items; let last1 = last;" [ready]="last1" [item]="item"></app-li-ready>
You will see the log in the console will print all the item string and then print the isReady.
I haven't yet looked in depth of how ngFor renders elements under the hood. But from observation, I've noticed it often tends to evaluate expressions more than once per each item it's iterating.
This causes any typescript method call made when checking ngFor 'last' variable to get, sometimes, triggered more than once.
To guarantee a one call to your typescript method by ngFor when it properly finishes iterating through items, you need to add a small protection against the multiple expression re-evaluation that ngFor does under the hood.
Here is one way to do it (via a directive), hope it helps:
The directive code
import { Directive, OnDestroy, Input, AfterViewInit } from '#angular/core';
#Directive({
selector: '[callback]'
})
export class CallbackDirective implements AfterViewInit, OnDestroy {
is_init:boolean = false;
called:boolean = false;
#Input('callback') callback:()=>any;
constructor() { }
ngAfterViewInit():void{
this.is_init = true;
}
ngOnDestroy():void {
this.is_init = false;
this.called = false;
}
#Input('callback-condition')
set condition(value: any) {
if (value==false || this.called) return;
// in case callback-condition is set prior ngAfterViewInit is called
if (!this.is_init) {
setTimeout(()=>this.condition = value, 50);
return;
}
if (this.callback) {
this.callback();
this.called = true;
}
else console.error("callback is null");
}
}
After declaring the above directive in your module (assuming you know how to do so, if not, ask and I'll hopefully update this with a code snippet), here is how to use the directive with ngFor:
<li *ngFor="let item of some_list;let last = last;" [callback]="doSomething" [callback-condition]="last">{{item}}</li>
'doSomething' is the method name in your TypeScript file that you want to call when ngFor finishes iterating through items.
Note: 'doSomething' doesn't have brackets '()' here as we're just passing a reference to the typescript method and not actually calling it here.
And finally here is how 'doSomething' method looks like in your typescript file:
public doSomething=()=> {
console.log("triggered from the directive's parent component when ngFor finishes iterating");
}

Resources