├── .editorconfig
├── .gitignore
├── 3.1.1
├── README.md
├── angular.json
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json
├── karma.conf.js
├── package-lock.json
├── package.json
├── protractor.conf.js
├── src
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── dialogs
│ │ ├── add
│ │ │ ├── add.dialog.component.ts
│ │ │ ├── add.dialog.css
│ │ │ └── add.dialog.html
│ │ ├── delete
│ │ │ ├── delete.dialog.component.ts
│ │ │ ├── delete.dialog.css
│ │ │ └── delete.dialog.html
│ │ └── edit
│ │ │ ├── edit.dialog.component.ts
│ │ │ ├── edit.dialog.css
│ │ │ └── edit.dialog.html
│ ├── models
│ │ └── issue.ts
│ └── services
│ │ └── data.service.ts
├── assets
│ └── .gitkeep
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.scss
├── test.ts
├── tsconfig.app.json
├── tsconfig.spec.json
└── typings.d.ts
├── tsconfig.json
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.angular/cache
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | testem.log
35 | /typings
36 |
37 | # e2e
38 | /e2e/*.js
39 | /e2e/*.map
40 |
41 | # System Files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/3.1.1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinantonio/angular-mat-table-crud/d9caccba12c2381c2cdd6a44e680d36e7d530143/3.1.1
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CRUD for Angular Material Table
2 |
3 | Project showcasing my CRUD (Create, Read, Update, Delete) implementation on Angular Mat-Table. Most importantly frontend updates accordingly
4 | with operations. This is important if you're using data from backend (some DB like MySQL) but I guess It can be used for local generated data
5 | as well.
6 |
7 | **Project is updated and now runs on Angular version 13 including Angular Material 13.**
8 | * For Angular 6 clone [angular_6 branch](https://github.com/marinantonio/angular-mat-table-crud/tree/angular_6).
9 | * For Angular 4 clone [angular_4 branch](https://github.com/marinantonio/angular-mat-table-crud/tree/angular_4).
10 |
11 | ## Screenshots
12 |
13 | Code in action:
14 |
15 | 
16 |
17 |
18 | ## REST API
19 | Here's a sample from my real-life application (sorry for Croatian):
20 |
21 | 
22 |
23 | Angular app using PHP RESTful API does backend updates to MySQL DB.
24 | You can find entire HttpClient REST code from this project inside dataService.
25 |
26 | ## Demo
27 |
28 | You can play around with code demo [here](https://marinantonio.github.io/angular-mat-table-crud/).
29 |
30 | ## Refresh function
31 |
32 | Material Table updates if you do a pagination or filter update. You can trigger that with simple method
33 | as follows:
34 |
35 | ```
36 | private refreshTable() {
37 | this.paginator._changePageSize(this.paginator.pageSize);
38 | }
39 | ```
40 | Credits to [yeager-j](https://github.com/marinantonio/angular-mat-table-crud/issues/12) for providing the refresh function
41 |
42 | Old method:
43 | ```
44 | private refreshTable() {
45 | // if there's a paginator active we're using it for refresh
46 | if (this.dataSource._paginator.hasNextPage()) {
47 | this.dataSource._paginator.nextPage();
48 | this.dataSource._paginator.previousPage();
49 | // in case we're on last page this if will tick
50 | } else if (this.dataSource._paginator.hasPreviousPage()) {
51 | this.dataSource._paginator.previousPage();
52 | this.dataSource._paginator.nextPage();
53 | // in all other cases including active filter we do it like this
54 | } else {
55 | this.dataSource.filter = '';
56 | this.dataSource.filter = this.filter.nativeElement.value;
57 | }
58 | }
59 | ```
60 |
61 | In case you have smaller dataset without need for a paginator you can update just using filter:
62 |
63 | ```
64 | private refreshTable() {
65 | // if there's nothing in filter
66 | if (this.dataSource._filterChange.getValue() === '') {
67 | this.dataSource.filter = ' ';
68 | this.dataSource.filter = '';
69 | } else {
70 | // if there's something, we make a simple change and then put back old value
71 | this.dataSource.filter = '';
72 | this.dataSource.filter = this.filter.nativeElement.value;
73 | }
74 | }
75 | ```
76 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "mat-table-crud": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "architect": {
11 | "build": {
12 | "builder": "@angular-devkit/build-angular:browser",
13 | "options": {
14 | "aot": true,
15 | "outputPath": "dist",
16 | "index": "src/index.html",
17 | "main": "src/main.ts",
18 | "tsConfig": "src/tsconfig.app.json",
19 | "polyfills": "src/polyfills.ts",
20 | "assets": [
21 | "src/assets",
22 | "src/favicon.ico"
23 | ],
24 | "styles": [
25 | "src/styles.scss"
26 | ],
27 | "scripts": []
28 | },
29 | "configurations": {
30 | "production": {
31 | "budgets": [
32 | {
33 | "type": "anyComponentStyle",
34 | "maximumWarning": "6kb"
35 | }
36 | ],
37 | "optimization": true,
38 | "outputHashing": "all",
39 | "sourceMap": false,
40 | "namedChunks": false,
41 | "aot": true,
42 | "extractLicenses": true,
43 | "vendorChunk": false,
44 | "buildOptimizer": true,
45 | "fileReplacements": [
46 | {
47 | "replace": "src/environments/environment.ts",
48 | "with": "src/environments/environment.prod.ts"
49 | }
50 | ]
51 | }
52 | }
53 | },
54 | "serve": {
55 | "builder": "@angular-devkit/build-angular:dev-server",
56 | "options": {
57 | "browserTarget": "mat-table-crud:build"
58 | },
59 | "configurations": {
60 | "production": {
61 | "browserTarget": "mat-table-crud:build:production"
62 | }
63 | }
64 | },
65 | "extract-i18n": {
66 | "builder": "@angular-devkit/build-angular:extract-i18n",
67 | "options": {
68 | "browserTarget": "mat-table-crud:build"
69 | }
70 | },
71 | "test": {
72 | "builder": "@angular-devkit/build-angular:karma",
73 | "options": {
74 | "main": "src/test.ts",
75 | "karmaConfig": "./karma.conf.js",
76 | "polyfills": "src/polyfills.ts",
77 | "tsConfig": "src/tsconfig.spec.json",
78 | "scripts": [],
79 | "styles": [
80 | "src/styles.scss"
81 | ],
82 | "assets": [
83 | "src/assets",
84 | "src/favicon.ico"
85 | ]
86 | }
87 | }
88 | }
89 | },
90 | "mat-table-crud-e2e": {
91 | "root": "e2e",
92 | "sourceRoot": "e2e",
93 | "projectType": "application",
94 | "architect": {
95 | "e2e": {
96 | "builder": "@angular-devkit/build-angular:protractor",
97 | "options": {
98 | "protractorConfig": "./protractor.conf.js",
99 | "devServerTarget": "mat-table-crud:serve"
100 | }
101 | }
102 | }
103 | }
104 | },
105 | "defaultProject": "mat-table-crud",
106 | "schematics": {
107 | "@schematics/angular:component": {
108 | "prefix": "app",
109 | "style": "css"
110 | },
111 | "@schematics/angular:directive": {
112 | "prefix": "app"
113 | }
114 | },
115 | "cli": {
116 | "analytics": false
117 | }
118 | }
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('mat-table-crud App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to app!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
20 | fixWebpackSourcePaths: true
21 | },
22 |
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mat-table-crud",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "ng build",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "private": true,
14 | "dependencies": {
15 | "@angular/animations": "~13.2.2",
16 | "@angular/cdk": "~13.2.2",
17 | "@angular/common": "~13.2.2",
18 | "@angular/compiler": "~13.2.2",
19 | "@angular/core": "~13.2.2",
20 | "@angular/forms": "~13.2.2",
21 | "@angular/material": "~13.2.2",
22 | "@angular/platform-browser": "~13.2.2",
23 | "@angular/platform-browser-dynamic": "~13.2.2",
24 | "@angular/router": "~13.2.2",
25 | "core-js": "^3.6.4",
26 | "rxjs": "^6.5.4",
27 | "tslib": "^1.10.0",
28 | "zone.js": "~0.11.4"
29 | },
30 | "devDependencies": {
31 | "@angular-devkit/build-angular": "~13.3.9",
32 | "@angular/cli": "~13.2.3",
33 | "@angular/compiler-cli": "~13.2.2",
34 | "@angular/language-service": "~13.2.2",
35 | "@types/jasmine": "^3.9.1",
36 | "@types/jasminewd2": "^2.0.8",
37 | "@types/node": "^12.11.1",
38 | "jasmine-spec-reporter": "~4.2.1",
39 | "karma": "^6.3.16",
40 | "karma-chrome-launcher": "~3.0.0",
41 | "karma-cli": "~2.0.0",
42 | "karma-coverage-istanbul-reporter": "^2.1.1",
43 | "karma-jasmine": "~2.0.1",
44 | "karma-jasmine-html-reporter": "^1.5.2",
45 | "protractor": "^7.0.0",
46 | "ts-node": "~8.3.0",
47 | "tslint": "~6.1.3",
48 | "typescript": "~4.5.5"
49 | }
50 | }
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | /* Structure */
2 | .container {
3 | display: flex;
4 | flex-direction: column;
5 | }
6 |
7 | /* Toolbar */
8 | .spacer {
9 | flex: 1 1 auto;
10 | }
11 |
12 | /* Filter */
13 | .form {
14 | min-height: 56px;
15 | max-height: 56px;
16 | display: flex;
17 | align-items: center;
18 | padding: 8px 24px 0;
19 | font-size: 20px;
20 | justify-content: space-between;
21 | border-bottom: 1px solid transparent;
22 | }
23 |
24 | .mat-form-field {
25 | font-size: 14px;
26 | flex-grow: 1;
27 | margin-top: 8px;
28 | }
29 |
30 | /* Mat table */
31 | .no-results {
32 | display: flex;
33 | justify-content: center;
34 | padding: 14px;
35 | font-size: 14px;
36 | font-style: italic;
37 | }
38 |
39 | .mat-cell:nth-child(1),
40 | .mat-header-cell:nth-child(1){
41 | flex: 0 0 6%;
42 | }
43 |
44 | .mat-cell:nth-child(2),
45 | .mat-header-cell:nth-child(2){
46 | flex: 0 0 30%;
47 | }
48 |
49 | .mat-cell:nth-child(3),
50 | .mat-header-cell:nth-child(3){
51 | flex: 0 0 6%;
52 | }
53 |
54 | .mat-cell:nth-child(7),
55 | .mat-header-cell:nth-child(7){
56 | flex: 0 0 7%;
57 | }
58 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 | Angular 13 MatTable CRUD Example
3 |
4 | Reload data:
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ng update @angular/cli @angular/core
20 |
22 |
23 |
24 |
25 | Id
26 | {{row.id}}
27 |
28 |
29 |
30 | Title
31 | {{row.title}}
32 |
33 |
34 |
35 | State
36 | {{row.state}}
37 |
38 |
39 |
40 | Url
41 | {{row.url}}
42 |
43 |
44 |
45 | Created at
46 | {{row.created_at}}
47 |
48 |
49 |
50 | Updated at
51 | {{row.updated_at}}
52 |
53 |
54 |
55 |
56 |
57 |
60 |
61 |
62 |
63 |
66 |
67 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | No results
80 |
81 |
82 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 | describe('AppComponent', () => {
4 | beforeEach(async(() => {
5 | TestBed.configureTestingModule({
6 | declarations: [
7 | AppComponent
8 | ],
9 | }).compileComponents();
10 | }));
11 | it('should create the app', async(() => {
12 | const fixture = TestBed.createComponent(AppComponent);
13 | const app = fixture.debugElement.componentInstance;
14 | expect(app).toBeTruthy();
15 | }));
16 | it(`should have as title 'app'`, async(() => {
17 | const fixture = TestBed.createComponent(AppComponent);
18 | const app = fixture.debugElement.componentInstance;
19 | expect(app.title).toEqual('app');
20 | }));
21 | it('should render title in a h1 tag', async(() => {
22 | const fixture = TestBed.createComponent(AppComponent);
23 | fixture.detectChanges();
24 | const compiled = fixture.debugElement.nativeElement;
25 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
26 | }));
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
2 | import {DataService} from './services/data.service';
3 | import {HttpClient} from '@angular/common/http';
4 | import {MatDialog} from '@angular/material/dialog';
5 | import {MatPaginator} from '@angular/material/paginator';
6 | import {MatSort} from '@angular/material/sort';
7 | import {Issue} from './models/issue';
8 | import {DataSource} from '@angular/cdk/collections';
9 | import {AddDialogComponent} from './dialogs/add/add.dialog.component';
10 | import {EditDialogComponent} from './dialogs/edit/edit.dialog.component';
11 | import {DeleteDialogComponent} from './dialogs/delete/delete.dialog.component';
12 | import {BehaviorSubject, fromEvent, merge, Observable} from 'rxjs';
13 | import {map} from 'rxjs/operators';
14 |
15 | @Component({
16 | selector: 'app-root',
17 | templateUrl: './app.component.html',
18 | styleUrls: ['./app.component.css']
19 | })
20 |
21 | export class AppComponent implements OnInit {
22 | displayedColumns = ['id', 'title', 'state', 'url', 'created_at', 'updated_at', 'actions'];
23 | exampleDatabase: DataService | null;
24 | dataSource: ExampleDataSource | null;
25 | index: number;
26 | id: number;
27 |
28 | constructor(public httpClient: HttpClient,
29 | public dialog: MatDialog,
30 | public dataService: DataService) {}
31 |
32 | @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
33 | @ViewChild(MatSort, {static: true}) sort: MatSort;
34 | @ViewChild('filter', {static: true}) filter: ElementRef;
35 |
36 | ngOnInit() {
37 | this.loadData();
38 | }
39 |
40 | refresh() {
41 | this.loadData();
42 | }
43 |
44 | addNew() {
45 | const dialogRef = this.dialog.open(AddDialogComponent, {
46 | data: {issue: Issue }
47 | });
48 |
49 | dialogRef.afterClosed().subscribe(result => {
50 | if (result === 1) {
51 | // After dialog is closed we're doing frontend updates
52 | // For add we're just pushing a new row inside DataService
53 | this.exampleDatabase.dataChange.value.push(this.dataService.getDialogData());
54 | this.refreshTable();
55 | }
56 | });
57 | }
58 |
59 | startEdit(i: number, id: number, title: string, state: string, url: string, created_at: string, updated_at: string) {
60 | this.id = id;
61 | // index row is used just for debugging proposes and can be removed
62 | this.index = i;
63 | console.log(this.index);
64 | const dialogRef = this.dialog.open(EditDialogComponent, {
65 | data: {id: id, title: title, state: state, url: url, created_at: created_at, updated_at: updated_at}
66 | });
67 |
68 | dialogRef.afterClosed().subscribe(result => {
69 | if (result === 1) {
70 | // When using an edit things are little different, firstly we find record inside DataService by id
71 | const foundIndex = this.exampleDatabase.dataChange.value.findIndex(x => x.id === this.id);
72 | // Then you update that record using data from dialogData (values you enetered)
73 | this.exampleDatabase.dataChange.value[foundIndex] = this.dataService.getDialogData();
74 | // And lastly refresh table
75 | this.refreshTable();
76 | }
77 | });
78 | }
79 |
80 | deleteItem(i: number, id: number, title: string, state: string, url: string) {
81 | this.index = i;
82 | this.id = id;
83 | const dialogRef = this.dialog.open(DeleteDialogComponent, {
84 | data: {id: id, title: title, state: state, url: url}
85 | });
86 |
87 | dialogRef.afterClosed().subscribe(result => {
88 | if (result === 1) {
89 | const foundIndex = this.exampleDatabase.dataChange.value.findIndex(x => x.id === this.id);
90 | // for delete we use splice in order to remove single object from DataService
91 | this.exampleDatabase.dataChange.value.splice(foundIndex, 1);
92 | this.refreshTable();
93 | }
94 | });
95 | }
96 |
97 |
98 | private refreshTable() {
99 | // Refreshing table using paginator
100 | // Thanks yeager-j for tips
101 | // https://github.com/marinantonio/angular-mat-table-crud/issues/12
102 | this.paginator._changePageSize(this.paginator.pageSize);
103 | }
104 |
105 |
106 | /* // If you don't need a filter or a pagination this can be simplified, you just use code from else block
107 | // OLD METHOD:
108 | // if there's a paginator active we're using it for refresh
109 | if (this.dataSource._paginator.hasNextPage()) {
110 | this.dataSource._paginator.nextPage();
111 | this.dataSource._paginator.previousPage();
112 | // in case we're on last page this if will tick
113 | } else if (this.dataSource._paginator.hasPreviousPage()) {
114 | this.dataSource._paginator.previousPage();
115 | this.dataSource._paginator.nextPage();
116 | // in all other cases including active filter we do it like this
117 | } else {
118 | this.dataSource.filter = '';
119 | this.dataSource.filter = this.filter.nativeElement.value;
120 | }*/
121 |
122 |
123 |
124 | public loadData() {
125 | this.exampleDatabase = new DataService(this.httpClient);
126 | this.dataSource = new ExampleDataSource(this.exampleDatabase, this.paginator, this.sort);
127 | fromEvent(this.filter.nativeElement, 'keyup')
128 | // .debounceTime(150)
129 | // .distinctUntilChanged()
130 | .subscribe(() => {
131 | if (!this.dataSource) {
132 | return;
133 | }
134 | this.dataSource.filter = this.filter.nativeElement.value;
135 | });
136 | }
137 | }
138 |
139 | export class ExampleDataSource extends DataSource {
140 | _filterChange = new BehaviorSubject('');
141 |
142 | get filter(): string {
143 | return this._filterChange.value;
144 | }
145 |
146 | set filter(filter: string) {
147 | this._filterChange.next(filter);
148 | }
149 |
150 | filteredData: Issue[] = [];
151 | renderedData: Issue[] = [];
152 |
153 | constructor(public _exampleDatabase: DataService,
154 | public _paginator: MatPaginator,
155 | public _sort: MatSort) {
156 | super();
157 | // Reset to the first page when the user changes the filter.
158 | this._filterChange.subscribe(() => this._paginator.pageIndex = 0);
159 | }
160 |
161 | /** Connect function called by the table to retrieve one stream containing the data to render. */
162 | connect(): Observable {
163 | // Listen for any changes in the base data, sorting, filtering, or pagination
164 | const displayDataChanges = [
165 | this._exampleDatabase.dataChange,
166 | this._sort.sortChange,
167 | this._filterChange,
168 | this._paginator.page
169 | ];
170 |
171 | this._exampleDatabase.getAllIssues();
172 |
173 |
174 | return merge(...displayDataChanges).pipe(map( () => {
175 | // Filter data
176 | this.filteredData = this._exampleDatabase.data.slice().filter((issue: Issue) => {
177 | const searchStr = (issue.id + issue.title + issue.url + issue.created_at).toLowerCase();
178 | return searchStr.indexOf(this.filter.toLowerCase()) !== -1;
179 | });
180 |
181 | // Sort filtered data
182 | const sortedData = this.sortData(this.filteredData.slice());
183 |
184 | // Grab the page's slice of the filtered sorted data.
185 | const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
186 | this.renderedData = sortedData.splice(startIndex, this._paginator.pageSize);
187 | return this.renderedData;
188 | }
189 | ));
190 | }
191 |
192 | disconnect() {}
193 |
194 |
195 | /** Returns a sorted copy of the database data. */
196 | sortData(data: Issue[]): Issue[] {
197 | if (!this._sort.active || this._sort.direction === '') {
198 | return data;
199 | }
200 |
201 | return data.sort((a, b) => {
202 | let propertyA: number | string = '';
203 | let propertyB: number | string = '';
204 |
205 | switch (this._sort.active) {
206 | case 'id': [propertyA, propertyB] = [a.id, b.id]; break;
207 | case 'title': [propertyA, propertyB] = [a.title, b.title]; break;
208 | case 'state': [propertyA, propertyB] = [a.state, b.state]; break;
209 | case 'url': [propertyA, propertyB] = [a.url, b.url]; break;
210 | case 'created_at': [propertyA, propertyB] = [a.created_at, b.created_at]; break;
211 | case 'updated_at': [propertyA, propertyB] = [a.updated_at, b.updated_at]; break;
212 | }
213 |
214 | const valueA = isNaN(+propertyA) ? propertyA : +propertyA;
215 | const valueB = isNaN(+propertyB) ? propertyB : +propertyB;
216 |
217 | return (valueA < valueB ? -1 : 1) * (this._sort.direction === 'asc' ? 1 : -1);
218 | });
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 |
4 |
5 | import { AppComponent } from './app.component';
6 | import {HttpClientModule} from '@angular/common/http';
7 | import { MatButtonModule } from '@angular/material/button';
8 | import { MatDialogModule } from '@angular/material/dialog';
9 | import { MatIconModule } from '@angular/material/icon';
10 | import { MatInputModule } from '@angular/material/input';
11 | import { MatPaginatorModule } from '@angular/material/paginator';
12 | import { MatSortModule } from '@angular/material/sort';
13 | import { MatTableModule } from '@angular/material/table';
14 | import { MatToolbarModule } from '@angular/material/toolbar';
15 | import {DataService} from './services/data.service';
16 | import {AddDialogComponent} from './dialogs/add/add.dialog.component';
17 | import {EditDialogComponent} from './dialogs/edit/edit.dialog.component';
18 | import {DeleteDialogComponent} from './dialogs/delete/delete.dialog.component';
19 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
20 | import {FormsModule, ReactiveFormsModule} from '@angular/forms';
21 |
22 |
23 | @NgModule({
24 | declarations: [
25 | AppComponent,
26 | AddDialogComponent,
27 | EditDialogComponent,
28 | DeleteDialogComponent
29 | ],
30 | imports: [
31 | BrowserModule,
32 | BrowserAnimationsModule,
33 | HttpClientModule,
34 | MatDialogModule,
35 | FormsModule,
36 | MatButtonModule,
37 | MatInputModule,
38 | MatIconModule,
39 | MatSortModule,
40 | MatTableModule,
41 | MatToolbarModule,
42 | MatPaginatorModule,
43 | ReactiveFormsModule
44 | ],
45 | providers: [
46 | DataService
47 | ],
48 | bootstrap: [AppComponent]
49 | })
50 | export class AppModule { }
51 |
--------------------------------------------------------------------------------
/src/app/dialogs/add/add.dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
2 | import {Component, Inject} from '@angular/core';
3 | import {DataService} from '../../services/data.service';
4 | import {FormControl, Validators} from '@angular/forms';
5 | import {Issue} from '../../models/issue';
6 |
7 | @Component({
8 | selector: 'app-add.dialog',
9 | templateUrl: '../../dialogs/add/add.dialog.html',
10 | styleUrls: ['../../dialogs/add/add.dialog.css']
11 | })
12 |
13 | export class AddDialogComponent {
14 | constructor(public dialogRef: MatDialogRef,
15 | @Inject(MAT_DIALOG_DATA) public data: Issue,
16 | public dataService: DataService) { }
17 |
18 | formControl = new FormControl('', [
19 | Validators.required
20 | // Validators.email,
21 | ]);
22 |
23 | getErrorMessage() {
24 | return this.formControl.hasError('required') ? 'Required field' :
25 | this.formControl.hasError('email') ? 'Not a valid email' :
26 | '';
27 | }
28 |
29 | submit() {
30 | // empty stuff
31 | }
32 |
33 | onNoClick(): void {
34 | this.dialogRef.close();
35 | }
36 |
37 | public confirmAdd(): void {
38 | this.dataService.addIssue(this.data);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/dialogs/add/add.dialog.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | min-width: 450px;
5 | }
6 |
7 | .container > * {
8 | width: 100%;
9 | }
10 |
11 | .form {
12 | display: flex;
13 | padding-top: 6px;
14 | }
15 |
16 | .mat-form-field {
17 | font-size: 16px;
18 | flex-grow: 1;
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/dialogs/add/add.dialog.html:
--------------------------------------------------------------------------------
1 |
2 |
Add new Issue
3 |
4 |
53 |
54 |
--------------------------------------------------------------------------------
/src/app/dialogs/delete/delete.dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
2 | import {Component, Inject} from '@angular/core';
3 | import {DataService} from '../../services/data.service';
4 |
5 |
6 | @Component({
7 | selector: 'app-delete.dialog',
8 | templateUrl: '../../dialogs/delete/delete.dialog.html',
9 | styleUrls: ['../../dialogs/delete/delete.dialog.css']
10 | })
11 | export class DeleteDialogComponent {
12 |
13 | constructor(public dialogRef: MatDialogRef,
14 | @Inject(MAT_DIALOG_DATA) public data: any, public dataService: DataService) { }
15 |
16 | onNoClick(): void {
17 | this.dialogRef.close();
18 | }
19 |
20 | confirmDelete(): void {
21 | this.dataService.deleteIssue(this.data.id);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/dialogs/delete/delete.dialog.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .container > * {
7 | width: 100%;
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/dialogs/delete/delete.dialog.html:
--------------------------------------------------------------------------------
1 |
2 |
Are you sure?
3 |
4 | Id: {{data.id}}
5 |
6 | Title: {{data.title}}
7 |
8 | State: {{data.state}}
9 |
10 | Url: {{data.url}}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/app/dialogs/edit/edit.dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
2 | import {Component, Inject} from '@angular/core';
3 | import {DataService} from '../../services/data.service';
4 | import {FormControl, Validators} from '@angular/forms';
5 |
6 | @Component({
7 | selector: 'app-baza.dialog',
8 | templateUrl: '../../dialogs/edit/edit.dialog.html',
9 | styleUrls: ['../../dialogs/edit/edit.dialog.css']
10 | })
11 | export class EditDialogComponent {
12 |
13 | constructor(public dialogRef: MatDialogRef,
14 | @Inject(MAT_DIALOG_DATA) public data: any, public dataService: DataService) { }
15 |
16 | formControl = new FormControl('', [
17 | Validators.required
18 | // Validators.email,
19 | ]);
20 |
21 | getErrorMessage() {
22 | return this.formControl.hasError('required') ? 'Required field' :
23 | this.formControl.hasError('email') ? 'Not a valid email' :
24 | '';
25 | }
26 |
27 | submit() {
28 | // emppty stuff
29 | }
30 |
31 | onNoClick(): void {
32 | this.dialogRef.close();
33 | }
34 |
35 | stopEdit(): void {
36 | this.dataService.updateIssue(this.data);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/dialogs/edit/edit.dialog.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | min-width: 450px;
5 | }
6 |
7 | .container > * {
8 | width: 100%;
9 | }
10 |
11 | .form {
12 | display: flex;
13 | padding-top: 6px;
14 | }
15 |
16 | .mat-form-field {
17 | font-size: 16px;
18 | flex-grow: 1;
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/dialogs/edit/edit.dialog.html:
--------------------------------------------------------------------------------
1 |
54 |
--------------------------------------------------------------------------------
/src/app/models/issue.ts:
--------------------------------------------------------------------------------
1 | export class Issue {
2 | id: number;
3 | title: string;
4 | state: string;
5 | url: string;
6 | created_at: string;
7 | updated_at: string;
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/services/data.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {BehaviorSubject} from 'rxjs';
3 | import {Issue} from '../models/issue';
4 | import {HttpClient, HttpErrorResponse} from '@angular/common/http';
5 |
6 | @Injectable()
7 | export class DataService {
8 | private readonly API_URL = 'https://api.github.com/repos/angular/angular/issues';
9 |
10 | dataChange: BehaviorSubject = new BehaviorSubject([]);
11 | // Temporarily stores data from dialogs
12 | dialogData: any;
13 |
14 | constructor (private httpClient: HttpClient) {}
15 |
16 | get data(): Issue[] {
17 | return this.dataChange.value;
18 | }
19 |
20 | getDialogData() {
21 | return this.dialogData;
22 | }
23 |
24 | /** CRUD METHODS */
25 | getAllIssues(): void {
26 | this.httpClient.get(this.API_URL).subscribe(data => {
27 | this.dataChange.next(data);
28 | },
29 | (error: HttpErrorResponse) => {
30 | console.log (error.name + ' ' + error.message);
31 | });
32 | }
33 |
34 | // DEMO ONLY, you can find working methods below
35 | addIssue (issue: Issue): void {
36 | this.dialogData = issue;
37 | }
38 |
39 | updateIssue (issue: Issue): void {
40 | this.dialogData = issue;
41 | }
42 |
43 | deleteIssue (id: number): void {
44 | console.log(id);
45 | }
46 | }
47 |
48 |
49 |
50 | /* REAL LIFE CRUD Methods I've used in my projects. ToasterService uses Material Toasts for displaying messages:
51 |
52 | // ADD, POST METHOD
53 | addItem(kanbanItem: KanbanItem): void {
54 | this.httpClient.post(this.API_URL, kanbanItem).subscribe(data => {
55 | this.dialogData = kanbanItem;
56 | this.toasterService.showToaster('Successfully added', 3000);
57 | },
58 | (err: HttpErrorResponse) => {
59 | this.toasterService.showToaster('Error occurred. Details: ' + err.name + ' ' + err.message, 8000);
60 | });
61 | }
62 |
63 | // UPDATE, PUT METHOD
64 | updateItem(kanbanItem: KanbanItem): void {
65 | this.httpClient.put(this.API_URL + kanbanItem.id, kanbanItem).subscribe(data => {
66 | this.dialogData = kanbanItem;
67 | this.toasterService.showToaster('Successfully edited', 3000);
68 | },
69 | (err: HttpErrorResponse) => {
70 | this.toasterService.showToaster('Error occurred. Details: ' + err.name + ' ' + err.message, 8000);
71 | }
72 | );
73 | }
74 |
75 | // DELETE METHOD
76 | deleteItem(id: number): void {
77 | this.httpClient.delete(this.API_URL + id).subscribe(data => {
78 | console.log(data['']);
79 | this.toasterService.showToaster('Successfully deleted', 3000);
80 | },
81 | (err: HttpErrorResponse) => {
82 | this.toasterService.showToaster('Error occurred. Details: ' + err.name + ' ' + err.message, 8000);
83 | }
84 | );
85 | }
86 | */
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinantonio/angular-mat-table-crud/d9caccba12c2381c2cdd6a44e680d36e7d530143/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinantonio/angular-mat-table-crud/d9caccba12c2381c2cdd6a44e680d36e7d530143/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular MatTable CRUD Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.log(err));
13 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | /***************************************************************************************************
7 | * Zone JS is required by default for Angular itself.
8 | */
9 | import 'zone.js/dist/zone'; // Included with Angular CLI.
10 |
11 |
12 |
13 | /***************************************************************************************************
14 | * APPLICATION IMPORTS
15 | */
16 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @import '@angular/material/theming';
3 | @import url('//fonts.googleapis.com/icon?family=Material+Icons');
4 |
5 | @include mat-core();
6 |
7 | $primary: mat-palette($mat-indigo, 500);
8 | $accent: mat-palette($mat-pink, 500, A200, A400);
9 | $warn: mat-palette($mat-red, 500);
10 |
11 | $config: mat-typography-config();
12 |
13 | $theme: mat-light-theme($primary, $accent, $warn);
14 |
15 | @include angular-material-theme($theme);
16 |
17 | body {
18 | font-family: mat-font-family($config);
19 | font-weight: normal;
20 | margin: 0;
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare const __karma__: any;
17 | declare const require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting(), {
26 | teardown: { destroyAfterEach: false }
27 | }
28 | );
29 | // Then we find all the tests.
30 | const context = require.context('./', true, /\.spec\.ts$/);
31 | // And load the modules.
32 | context.keys().map(context);
33 | // Finally, start Karma to run the tests.
34 | __karma__.start();
35 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "baseUrl": "./",
6 | "types": []
7 | },
8 | "files": [
9 | "main.ts",
10 | "polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "types": [
7 | "jasmine",
8 | "node"
9 | ]
10 | },
11 | "files": [
12 | "test.ts",
13 | "polyfills.ts"
14 | ],
15 | "include": [
16 | "**/*.spec.ts",
17 | "**/*.d.ts"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "downlevelIteration": true,
5 | "importHelpers": true,
6 | "outDir": "./dist/out-tsc",
7 | "sourceMap": true,
8 | "declaration": false,
9 | "moduleResolution": "node",
10 | "emitDecoratorMetadata": true,
11 | "experimentalDecorators": true,
12 | "target": "es2015",
13 | "typeRoots": [
14 | "node_modules/@types"
15 | ],
16 | "lib": [
17 | "es2017",
18 | "dom"
19 | ],
20 | "module": "esnext",
21 | "baseUrl": "./"
22 | }
23 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer",
4 | "node_modules/rxjs-tslint"
5 | ],
6 | "rules": {
7 | "rxjs-collapse-imports": true,
8 | "rxjs-pipeable-operators-only": true,
9 | "rxjs-no-static-observable-methods": true,
10 | "rxjs-proper-imports": true,
11 | "arrow-return-shorthand": true,
12 | "callable-types": true,
13 | "class-name": true,
14 | "comment-format": [
15 | true,
16 | "check-space"
17 | ],
18 | "curly": true,
19 | "deprecation": {
20 | "severity": "warn"
21 | },
22 | "eofline": true,
23 | "forin": true,
24 | "import-blacklist": [
25 | true,
26 | "rxjs/Rx"
27 | ],
28 | "import-spacing": true,
29 | "indent": [
30 | true,
31 | "spaces"
32 | ],
33 | "interface-over-type-literal": true,
34 | "label-position": true,
35 | "max-line-length": [
36 | true,
37 | 140
38 | ],
39 | "member-access": false,
40 | "member-ordering": [
41 | true,
42 | {
43 | "order": [
44 | "static-field",
45 | "instance-field",
46 | "static-method",
47 | "instance-method"
48 | ]
49 | }
50 | ],
51 | "no-arg": true,
52 | "no-bitwise": true,
53 | "no-console": [
54 | true,
55 | "debug",
56 | "info",
57 | "time",
58 | "timeEnd",
59 | "trace"
60 | ],
61 | "no-construct": true,
62 | "no-debugger": true,
63 | "no-duplicate-super": true,
64 | "no-empty": false,
65 | "no-empty-interface": true,
66 | "no-eval": true,
67 | "no-inferrable-types": [
68 | true,
69 | "ignore-params"
70 | ],
71 | "no-misused-new": true,
72 | "no-non-null-assertion": true,
73 | "no-shadowed-variable": true,
74 | "no-string-literal": false,
75 | "no-string-throw": true,
76 | "no-switch-case-fall-through": true,
77 | "no-trailing-whitespace": true,
78 | "no-unnecessary-initializer": true,
79 | "no-unused-expression": true,
80 | "no-use-before-declare": true,
81 | "no-var-keyword": true,
82 | "object-literal-sort-keys": false,
83 | "one-line": [
84 | true,
85 | "check-open-brace",
86 | "check-catch",
87 | "check-else",
88 | "check-whitespace"
89 | ],
90 | "prefer-const": true,
91 | "quotemark": [
92 | true,
93 | "single"
94 | ],
95 | "radix": true,
96 | "semicolon": [
97 | true,
98 | "always"
99 | ],
100 | "triple-equals": [
101 | true,
102 | "allow-null-check"
103 | ],
104 | "typedef-whitespace": [
105 | true,
106 | {
107 | "call-signature": "nospace",
108 | "index-signature": "nospace",
109 | "parameter": "nospace",
110 | "property-declaration": "nospace",
111 | "variable-declaration": "nospace"
112 | }
113 | ],
114 | "typeof-compare": true,
115 | "unified-signatures": true,
116 | "variable-name": false,
117 | "whitespace": [
118 | true,
119 | "check-branch",
120 | "check-decl",
121 | "check-operator",
122 | "check-separator",
123 | "check-type"
124 | ],
125 | "directive-selector": [
126 | true,
127 | "attribute",
128 | "app",
129 | "camelCase"
130 | ],
131 | "component-selector": [
132 | true,
133 | "element",
134 | "app",
135 | "kebab-case"
136 | ],
137 | "no-output-on-prefix": true,
138 | "no-inputs-metadata-property": true,
139 | "no-outputs-metadata-property": true,
140 | "no-host-metadata-property": true,
141 | "no-input-rename": true,
142 | "no-output-rename": true,
143 | "use-lifecycle-interface": true,
144 | "use-pipe-transform-interface": true,
145 | "component-class-suffix": true,
146 | "directive-class-suffix": true
147 | }
148 | }
149 |
--------------------------------------------------------------------------------