-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(schematics): navigation schematic #10009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.sidenav-container { | ||
height: 100%; | ||
} | ||
|
||
.sidenav { | ||
width: 200px; | ||
box-shadow: 3px 0 6px rgba(0,0,0,.24); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<mat-sidenav-container class="sidenav-container"> | ||
<mat-sidenav | ||
#drawer | ||
class="sidenav" | ||
fixedInViewport="true" | ||
[attr.role]="isHandset ? 'dialog' : 'navigation'" | ||
[mode]="isHandset ? 'over' : 'side'" | ||
[opened]="!(isHandset | async)!.matches"> | ||
<mat-toolbar color="primary">Menu</mat-toolbar> | ||
<mat-nav-list> | ||
<a mat-list-item href="#">Link 1</a> | ||
<a mat-list-item href="#">Link 2</a> | ||
<a mat-list-item href="#">Link 3</a> | ||
</mat-nav-list> | ||
</mat-sidenav> | ||
<mat-sidenav-content> | ||
<mat-toolbar color="primary"> | ||
<button | ||
type="button" | ||
aria-label="Toggle sidenav" | ||
mat-icon-button | ||
(click)="drawer.toggle()" | ||
*ngIf="(isHandset | async)!.matches"> | ||
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon> | ||
</button> | ||
<span>Application Title</span> | ||
</mat-toolbar> | ||
</mat-sidenav-content> | ||
</mat-sidenav-container> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
|
||
import { fakeAsync, ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component'; | ||
|
||
describe('<%= classify(name) %>Component', () => { | ||
let component: <%= classify(name) %>Component; | ||
let fixture: ComponentFixture<<%= classify(name) %>Component>; | ||
|
||
beforeEach(fakeAsync(() => { | ||
TestBed.configureTestingModule({ | ||
declarations: [ <%= classify(name) %>Component ] | ||
}) | ||
.compileComponents(); | ||
|
||
fixture = TestBed.createComponent(<%= classify(name) %>Component); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
})); | ||
|
||
it('should compile', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { Component, <% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core'; | ||
import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout'; | ||
import { Observable } from 'rxjs/Observable'; | ||
|
||
@Component({ | ||
selector: '<%= selector %>',<% if(inlineTemplate) { %> | ||
template: ` | ||
<mat-sidenav-container class="sidenav-container"> | ||
<mat-sidenav | ||
#drawer | ||
class="sidenav" | ||
fixedInViewport="true" | ||
[attr.role]="isHandset ? 'dialog' : 'navigation'" | ||
[mode]="isHandset ? 'over' : 'side'" | ||
[opened]="!(isHandset | async)!.matches"> | ||
<mat-toolbar color="primary">Menu</mat-toolbar> | ||
<mat-nav-list> | ||
<a mat-list-item href="#">Link 1</a> | ||
<a mat-list-item href="#">Link 2</a> | ||
<a mat-list-item href="#">Link 3</a> | ||
</mat-nav-list> | ||
</mat-sidenav> | ||
<mat-sidenav-content> | ||
<mat-toolbar color="primary"> | ||
<button | ||
type="button" | ||
aria-label="Toggle sidenav" | ||
mat-icon-button | ||
(click)="drawer.toggle()" | ||
*ngIf="(isHandset | async)!.matches"> | ||
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon> | ||
</button> | ||
<span>Application Title</span> | ||
</mat-toolbar> | ||
</mat-sidenav-content> | ||
</mat-sidenav-container> | ||
`,<% } else { %> | ||
templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %> | ||
styles: [ | ||
` | ||
.sidenav-container { | ||
height: 100%; | ||
} | ||
|
||
.sidenav { | ||
width: 200px; | ||
box-shadow: 3px 0 6px rgba(0,0,0,.24); | ||
} | ||
` | ||
]<% } else { %> | ||
styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>']<% } %><% if(!!viewEncapsulation) { %>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was copied from the base create component schematic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we update the base schematic? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hansl ^^ |
||
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>, | ||
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %> | ||
}) | ||
export class <%= classify(name) %>Component { | ||
isHandset: Observable<BreakpointState> = this.breakpointObserver.observe(Breakpoints.Handset); | ||
constructor(private breakpointObserver: BreakpointObserver) {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import {chain, Rule, noop, Tree, SchematicContext} from '@angular-devkit/schematics'; | ||
import {Schema} from './schema'; | ||
import {addModuleImportToModule} from '../utils/ast'; | ||
import {findModuleFromOptions} from '../utils/devkit-utils/find-module'; | ||
import {buildComponent} from '../utils/devkit-utils/component'; | ||
|
||
/** | ||
* Scaffolds a new navigation component. | ||
* Internally it bootstraps the base component schematic | ||
*/ | ||
export default function(options: Schema): Rule { | ||
return chain([ | ||
buildComponent({ ...options }), | ||
options.skipImport ? noop() : addNavModulesToModule(options) | ||
]); | ||
} | ||
|
||
/** | ||
* Adds the required modules to the relative module. | ||
*/ | ||
function addNavModulesToModule(options: Schema) { | ||
return (host: Tree) => { | ||
const modulePath = findModuleFromOptions(host, options); | ||
addModuleImportToModule(host, modulePath, 'LayoutModule', '@angular/cdk/layout'); | ||
addModuleImportToModule(host, modulePath, 'MatToolbarModule', '@angular/material'); | ||
addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material'); | ||
addModuleImportToModule(host, modulePath, 'MatSidenavModule', '@angular/material'); | ||
addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material'); | ||
addModuleImportToModule(host, modulePath, 'MatListModule', '@angular/material'); | ||
return host; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing'; | ||
import {join} from 'path'; | ||
import {Tree} from '@angular-devkit/schematics'; | ||
import {createTestApp} from '../utils/testing'; | ||
import {getFileContent} from '@schematics/angular/utility/test'; | ||
|
||
const collectionPath = join(__dirname, '../collection.json'); | ||
|
||
describe('material-nav-schematic', () => { | ||
let runner: SchematicTestRunner; | ||
const options = { | ||
name: 'foo', | ||
path: 'app', | ||
sourceDir: 'src', | ||
inlineStyle: false, | ||
inlineTemplate: false, | ||
changeDetection: 'Default', | ||
styleext: 'css', | ||
spec: true, | ||
module: undefined, | ||
export: false, | ||
prefix: undefined, | ||
viewEncapsulation: undefined, | ||
}; | ||
|
||
beforeEach(() => { | ||
runner = new SchematicTestRunner('schematics', collectionPath); | ||
}); | ||
|
||
it('should create nav files and add them to module', () => { | ||
const tree = runner.runSchematic('materialNav', { ...options }, createTestApp()); | ||
const files = tree.files; | ||
|
||
expect(files).toContain('/src/app/foo/foo.component.css'); | ||
expect(files).toContain('/src/app/foo/foo.component.html'); | ||
expect(files).toContain('/src/app/foo/foo.component.spec.ts'); | ||
expect(files).toContain('/src/app/foo/foo.component.ts'); | ||
|
||
const moduleContent = getFileContent(tree, '/src/app/app.module.ts'); | ||
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.component'/); | ||
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooComponent\r?\n/m); | ||
}); | ||
|
||
it('should add nav imports to module', () => { | ||
const tree = runner.runSchematic('materialNav', { ...options }, createTestApp()); | ||
const moduleContent = getFileContent(tree, '/src/app/app.module.ts'); | ||
|
||
expect(moduleContent).toContain('LayoutModule'); | ||
expect(moduleContent).toContain('MatToolbarModule'); | ||
expect(moduleContent).toContain('MatButtonModule'); | ||
expect(moduleContent).toContain('MatSidenavModule'); | ||
expect(moduleContent).toContain('MatIconModule'); | ||
expect(moduleContent).toContain('MatListModule'); | ||
|
||
expect(moduleContent).toContain(`import { LayoutModule } from '@angular/cdk/layout';`); | ||
expect(moduleContent).toContain( | ||
// tslint:disable-next-line | ||
`import { MatToolbarModule, MatButtonModule, MatSidenavModule, MatIconModule, MatListModule } from '@angular/material';`); | ||
}); | ||
|
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import {Schema as ComponentSchema} from '@schematics/angular/component/schema'; | ||
|
||
export interface Schema extends ComponentSchema {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
{ | ||
"$schema": "http://json-schema.org/schema", | ||
"id": "SchematicsMaterialNav", | ||
"title": "Material Nav Options Schema", | ||
"type": "object", | ||
"properties": { | ||
"path": { | ||
"type": "string", | ||
"description": "The path to create the component.", | ||
"default": "app", | ||
"visible": false | ||
}, | ||
"sourceDir": { | ||
"type": "string", | ||
"description": "The path of the source directory.", | ||
"default": "src", | ||
"alias": "sd", | ||
"visible": false | ||
}, | ||
"appRoot": { | ||
"type": "string", | ||
"description": "The root of the application.", | ||
"visible": false | ||
}, | ||
"name": { | ||
"type": "string", | ||
"description": "The name of the component." | ||
}, | ||
"inlineStyle": { | ||
"description": "Specifies if the style will be in the ts file.", | ||
"type": "boolean", | ||
"default": false, | ||
"alias": "is" | ||
}, | ||
"inlineTemplate": { | ||
"description": "Specifies if the template will be in the ts file.", | ||
"type": "boolean", | ||
"default": false, | ||
"alias": "it" | ||
}, | ||
"viewEncapsulation": { | ||
"description": "Specifies the view encapsulation strategy.", | ||
"enum": ["Emulated", "Native", "None"], | ||
"type": "string", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "default": "Emulated" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was copied from the base create component schematic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really good enough reason on its own; Hans has mentioned that those base schematics are not meant to be normative. |
||
"default": "Emulated", | ||
"alias": "ve" | ||
}, | ||
"changeDetection": { | ||
"description": "Specifies the change detection strategy.", | ||
"enum": ["Default", "OnPush"], | ||
"type": "string", | ||
"default": "Default", | ||
"alias": "cd" | ||
}, | ||
"prefix": { | ||
"type": "string", | ||
"description": "The prefix to apply to generated selectors.", | ||
"default": "app", | ||
"alias": "p" | ||
}, | ||
"styleext": { | ||
"description": "The file extension to be used for style files.", | ||
"type": "string", | ||
"default": "css" | ||
}, | ||
"spec": { | ||
"type": "boolean", | ||
"description": "Specifies if a spec file is generated.", | ||
"default": true | ||
}, | ||
"flat": { | ||
"type": "boolean", | ||
"description": "Flag to indicate if a dir is created.", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unclear what "dir" it's referring to. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was copied from the base create component schematic. |
||
"default": false | ||
}, | ||
"skipImport": { | ||
"type": "boolean", | ||
"description": "Flag to skip the module import.", | ||
"default": false | ||
}, | ||
"selector": { | ||
"type": "string", | ||
"description": "The selector to use for the component." | ||
}, | ||
"module": { | ||
"type": "string", | ||
"description": "Allows specification of the declaring module.", | ||
"alias": "m" | ||
}, | ||
"export": { | ||
"type": "boolean", | ||
"default": false, | ||
"description": "Specifies if declaring module exports the component." | ||
} | ||
}, | ||
"required": [ | ||
"name" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,5 +17,8 @@ | |
"jasmine", | ||
"node" | ||
] | ||
} | ||
}, | ||
"exclude": [ | ||
"*/files/**/*" | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I guess schematics doesn't have a way to insert contents of another file here? Do they have a way to add comments that will be stripped out of the generated file? If so it would be good to add some comments for people maintaining this file that they need to update in 2 places
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya, its very painful. I'm thinking about making a util to handle this automatically.