chore: convert templateUrls to use moduleId where possible. (#2477)

This commit is contained in:
Ward Bell
2016-09-25 18:51:54 -07:00
committed by GitHub
parent 4a6f35f58b
commit 6def9505cc
68 changed files with 118 additions and 526 deletions
@@ -3,8 +3,9 @@ import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
moduleId: module.id,
selector: 'hero-detail',
templateUrl: 'app/hero-detail.component.html'
templateUrl: 'hero-detail.component.html'
})
export class HeroDetailComponent {
@Input() hero: Hero;
@@ -5,9 +5,10 @@ import { HeroService } from './hero.service';
// #docregion metadata, providers
@Component({
moduleId: module.id,
selector: 'hero-list',
templateUrl: 'app/hero-list.component.html',
providers: [ HeroService ]
templateUrl: 'hero-list.component.html',
providers: [ HeroService ]
})
// #enddocregion providers
// #docregion class
@@ -2,8 +2,9 @@
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html'
templateUrl: 'app.component.html'
})
export class AppComponent { }
@@ -4,10 +4,11 @@ import { MovieService } from './movie.service';
import { IMovie } from './movie';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html',
styleUrls: ['app/app.component.css'],
providers: [MovieService]
templateUrl: 'app.component.html',
styleUrls: [ 'app.component.css' ],
providers: [ MovieService ]
})
export class AppComponent {
@@ -8,11 +8,12 @@ import { MovieService } from './movie.service';
// #docregion component
@Component({
moduleId: module.id,
selector: 'movie-list',
templateUrl: 'app/movie-list.component.html',
templateUrl: 'movie-list.component.html',
// #enddocregion component
// #docregion style-url
styleUrls: ['app/movie-list.component.css'],
styleUrls: [ 'movie-list.component.css' ],
// #enddocregion style-url
})
// #enddocregion component
@@ -1,7 +1,8 @@
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html'
templateUrl: 'app.component.html'
})
export class AppComponent { }
@@ -7,10 +7,11 @@ import { UserContextService } from './user-context.service';
import { UserService } from './user.service';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html',
templateUrl: 'app.component.html',
// #docregion providers
providers: [LoggerService, UserContextService, UserService]
providers: [ LoggerService, UserContextService, UserService ]
// #enddocregion providers
})
export class AppComponent {
@@ -1,27 +0,0 @@
/// <reference path='../_protractor/e2e.d.ts' />
'use strict';
/* tslint:disable:quotemark */
describe('Dynamic Form Deprecated', function () {
beforeAll(function () {
browser.get('');
});
it('should submit form', function () {
let firstNameElement = element.all(by.css('input[id=firstName]')).get(0);
expect(firstNameElement.getAttribute('value')).toEqual('Bombasto');
let emailElement = element.all(by.css('input[id=emailAddress]')).get(0);
let email = 'test@test.com';
emailElement.sendKeys(email);
expect(emailElement.getAttribute('value')).toEqual(email);
element(by.css('select option[value="solid"]')).click();
let saveButton = element.all(by.css('button')).get(0);
saveButton.click().then(function(){
expect(element(by.xpath("//strong[contains(text(),'Saved the following values')]")).isPresent()).toBe(true);
});
});
});
@@ -1,24 +0,0 @@
// #docregion
import { Component } from '@angular/core';
import { DynamicFormComponent } from './dynamic-form.component';
import { QuestionService } from './question.service';
@Component({
selector: 'my-app',
template: `
<div>
<h2>Job Application for Heroes</h2>
<dynamic-form [questions]="questions"></dynamic-form>
</div>
`,
directives: [DynamicFormComponent],
providers: [QuestionService]
})
export class AppComponent {
questions: any[];
constructor(service: QuestionService) {
this.questions = service.getQuestions();
}
}
@@ -1,17 +0,0 @@
<!-- #docregion -->
<div [ngFormModel]="form">
<label [attr.for]="question.key">{{question.label}}</label>
<div [ngSwitch]="question.controlType">
<input *ngSwitchCase="'textbox'" [ngControl]="question.key"
[id]="question.key" [type]="question.type">
<select [id]="question.key" *ngSwitchCase="'dropdown'" [ngControl]="question.key">
<option *ngFor="let opt of question.options" [value]="opt.key">{{opt.value}}</option>
</select>
</div>
<div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
</div>
@@ -1,15 +0,0 @@
// #docregion
import { Component, Input } from '@angular/core';
import { ControlGroup } from '@angular/common';
import { QuestionBase } from './question-base';
@Component({
selector: 'df-question',
templateUrl: 'app/dynamic-form-question.component.html'
})
export class DynamicFormQuestionComponent {
@Input() question: QuestionBase<any>;
@Input() form: ControlGroup;
get isValid() { return this.form.controls[this.question.key].valid; }
}
@@ -1,17 +0,0 @@
<!-- #docregion -->
<div>
<form (ngSubmit)="onSubmit()" [ngFormModel]="form">
<div *ngFor="let question of questions" class="form-row">
<df-question [question]="question" [form]="form"></df-question>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid">Save</button>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
</div>
@@ -1,30 +0,0 @@
// #docregion
import { Component, Input, OnInit } from '@angular/core';
import { ControlGroup } from '@angular/common';
import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service';
import { DynamicFormQuestionComponent } from './dynamic-form-question.component';
@Component({
selector: 'dynamic-form',
templateUrl: 'app/dynamic-form.component.html',
directives: [DynamicFormQuestionComponent],
providers: [QuestionControlService]
})
export class DynamicFormComponent implements OnInit {
@Input() questions: QuestionBase<any>[] = [];
form: ControlGroup;
payLoad = '';
constructor(private qcs: QuestionControlService) { }
ngOnInit() {
this.form = this.qcs.toControlGroup(this.questions);
}
onSubmit() {
this.payLoad = JSON.stringify(this.form.value);
}
}
@@ -1,5 +0,0 @@
import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
bootstrap(AppComponent, []);
@@ -1,25 +0,0 @@
// #docregion
export class QuestionBase<T>{
value: T;
key: string;
label: string;
required: boolean;
order: number;
controlType: string;
constructor(options: {
value?: T,
key?: string,
label?: string,
required?: boolean,
order?: number,
controlType?: string
} = {}) {
this.value = options.value;
this.key = options.key || '';
this.label = options.label || '';
this.required = !!options.required;
this.order = options.order === undefined ? 1 : options.order;
this.controlType = options.controlType || '';
}
}
@@ -1,18 +0,0 @@
// #docregion
import { Injectable } from '@angular/core';
import { FormBuilder, Validators } from '@angular/common';
import { QuestionBase } from './question-base';
@Injectable()
export class QuestionControlService {
constructor(private fb: FormBuilder) { }
toControlGroup(questions: QuestionBase<any>[] ) {
let group = {};
questions.forEach(question => {
group[question.key] = question.required ? [question.value || '', Validators.required] : [question.value || ''];
});
return this.fb.group(group);
}
}
@@ -1,12 +0,0 @@
// #docregion
import { QuestionBase } from './question-base';
export class DropdownQuestion extends QuestionBase<string> {
controlType = 'dropdown';
options: {key: string, value: string}[] = [];
constructor(options: {} = {}) {
super(options);
this.options = options['options'] || [];
}
}
@@ -1,12 +0,0 @@
// #docregion
import { QuestionBase } from './question-base';
export class TextboxQuestion extends QuestionBase<string> {
controlType = 'textbox';
type: string;
constructor(options: {} = {}) {
super(options);
this.type = options['type'] || '';
}
}
@@ -1,47 +0,0 @@
// #docregion
import { Injectable } from '@angular/core';
import { QuestionBase } from './question-base';
import { TextboxQuestion } from './question-textbox';
import { DropdownQuestion } from './question-dropdown';
@Injectable()
export class QuestionService {
// Todo: get from a remote source of question metadata
// Todo: make asynchronous
getQuestions() {
let questions: QuestionBase<any>[] = [
new DropdownQuestion({
key: 'brave',
label: 'Bravery Rating',
options: [
{key: 'solid', value: 'Solid'},
{key: 'great', value: 'Great'},
{key: 'good', value: 'Good'},
{key: 'unproven', value: 'Unproven'}
],
order: 3
}),
new TextboxQuestion({
key: 'firstName',
label: 'First name',
value: 'Bombasto',
required: true,
order: 1
}),
new TextboxQuestion({
key: 'emailAddress',
label: 'Email',
type: 'email',
order: 2
})
];
return questions.sort((a, b) => a.order - b.order);
}
}
@@ -1,29 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<base href="/">
<title>Dynamic Form</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- #docregion style -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="sample.css">
<!-- #enddocregion style -->
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading app...</my-app>
</body>
</html>
@@ -1,9 +0,0 @@
{
"description": "Dynamic Form Deprecated",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1].*"
],
"tags":["cookbook"]
}
@@ -1,7 +0,0 @@
.errorMessage{
color:red;
}
.form-row{
margin-top: 10px;
}
@@ -1,12 +1,13 @@
// #docregion
import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormGroup } from '@angular/forms';
import { QuestionBase } from './question-base';
@Component({
moduleId: module.id,
selector: 'df-question',
templateUrl: 'app/dynamic-form-question.component.html'
templateUrl: 'dynamic-form-question.component.html'
})
export class DynamicFormQuestionComponent {
@Input() question: QuestionBase<any>;
@@ -6,8 +6,9 @@ import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service';
@Component({
moduleId: module.id,
selector: 'dynamic-form',
templateUrl: 'app/dynamic-form.component.html',
templateUrl: 'dynamic-form.component.html',
providers: [ QuestionControlService ]
})
export class DynamicFormComponent implements OnInit {
@@ -6,8 +6,9 @@ import { Component } from '@angular/core';
import { Hero } from './hero';
@Component({
moduleId: module.id,
selector: 'hero-form',
templateUrl: 'app/hero-form.component.html'
templateUrl: 'hero-form.component.html'
})
export class HeroFormComponent {
@@ -2,11 +2,14 @@
import { Component } from '@angular/core';
@Component({
// Set the base for module-relative URLs
moduleId: module.id,
// Declare the tag name in index.html to where the component attaches
selector: 'hello-world',
// Location of the template for this component
templateUrl: 'app/hello_world.html'
templateUrl: 'hello_world.html'
})
export class HelloWorldComponent {
@@ -1,7 +1,8 @@
// #docregion
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html'
templateUrl: 'app.component.html'
})
export class AppComponent { }
@@ -75,8 +75,9 @@ export class DoCheckComponent implements DoCheck {
/***************************************/
@Component({
moduleId: module.id,
selector: 'do-check-parent',
templateUrl: 'app/do-check-parent.component.html',
templateUrl: 'do-check-parent.component.html',
styles: ['.parent {background: Lavender}']
})
export class DoCheckParentComponent {
@@ -49,8 +49,9 @@ export class OnChangesComponent implements OnChanges {
/***************************************/
@Component({
moduleId: module.id,
selector: 'on-changes-parent',
templateUrl: 'app/on-changes-parent.component.html',
templateUrl: 'on-changes-parent.component.html',
styles: ['.parent {background: Lavender;}']
})
export class OnChangesParentComponent {
@@ -4,8 +4,9 @@ import { Component } from '@angular/core';
import { LoggerService } from './logger.service';
@Component({
moduleId: module.id,
selector: 'spy-parent',
templateUrl: 'app/spy.component.html',
templateUrl: 'spy.component.html',
styles: [
'.parent {background: khaki;}',
'.heroes {background: LightYellow; padding: 0 8px}'
@@ -8,7 +8,7 @@ import { UserService } from '../user.service';
moduleId: module.id,
selector: 'app-contact',
templateUrl: 'contact.component.html',
styleUrls: ['contact.component.css']
styleUrls: [ 'contact.component.css' ]
})
export class ContactComponent implements OnInit {
contact: Contact;
@@ -9,7 +9,7 @@ import { UserService } from '../core/user.service';
moduleId: module.id,
selector: 'app-contact',
templateUrl: 'contact.component.html',
styleUrls: ['contact.component.css']
styleUrls: [ 'contact.component.css' ]
})
export class ContactComponent implements OnInit {
contact: Contact;
@@ -2,8 +2,9 @@
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html'
templateUrl: 'app.component.html'
})
export class AppComponent {
birthday = new Date(1988, 3, 15); // April 15, 1988
@@ -5,8 +5,9 @@ import { Component } from '@angular/core';
import { HEROES } from './heroes';
@Component({
moduleId: module.id,
selector: 'flying-heroes',
templateUrl: 'app/flying-heroes.component.html',
templateUrl: 'flying-heroes.component.html',
styles: ['#flyers, #all {font-style: italic}']
})
// #docregion v1
@@ -49,8 +50,9 @@ export class FlyingHeroesComponent {
////// Identical except for impure pipe //////
// #docregion impure-component
@Component({
moduleId: module.id,
selector: 'flying-heroes-impure',
templateUrl: 'app/flying-heroes-impure.component.html',
templateUrl: 'flying-heroes-impure.component.html',
// #enddocregion impure-component
styles: ['.flyers, .all {font-style: italic}'],
// #docregion impure-component
@@ -5,7 +5,8 @@ import { DomSanitizer, SafeResourceUrl, SafeUrl } from '@angular/platform-browse
@Component({
selector: 'bypass-security',
templateUrl: 'app/bypass-security.component.html',
moduleId: module.id,
templateUrl: 'bypass-security.component.html',
})
export class BypassSecurityComponent {
dangerousUrl: string;
@@ -6,7 +6,8 @@ import { HeroService } from './hero.service.promise';
@Component({
selector: 'hero-list-promise',
templateUrl: 'app/toh/hero-list.component.html',
moduleId: module.id,
templateUrl: 'hero-list.component.html',
providers: [ HeroService ]
})
// #docregion component
@@ -5,8 +5,9 @@ import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'hero-list',
templateUrl: 'app/toh/hero-list.component.html',
templateUrl: 'hero-list.component.html',
providers: [ HeroService ]
})
// #docregion component
@@ -3,8 +3,9 @@
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'structural-directives',
templateUrl: 'app/structural-directives.component.html',
templateUrl: 'structural-directives.component.html',
styles: ['button { min-width: 100px; }']
})
export class StructuralDirectivesComponent {
@@ -3,9 +3,8 @@ import { Component, OnInit } from '@angular/core';
import { ToastService } from './toast.service';
@Component({
moduleId: module.id,
selector: 'toh-toast',
templateUrl: '<div>toast</div>'
template: '<div>toast</div>'
})
export class ToastComponent implements OnInit {
constructor(toastService: ToastService) { }
@@ -3,9 +3,8 @@ import { Component, OnInit } from '@angular/core';
import { ToastService } from './toast.service';
@Component({
moduleId: module.id,
selector: 'toh-toast',
templateUrl: '<div>toast</div>'
template: '<div>toast</div>'
})
export class ToastComponent implements OnInit {
constructor(toastService: ToastService) { }
@@ -1,7 +1,8 @@
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html'
templateUrl: 'app.component.html'
})
export class AppComponent { }
@@ -17,8 +17,9 @@ export enum Color {Red, Green, Blue};
* Giant grab bag of stuff to drive the chapter
*/
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html'
templateUrl: 'app.component.html'
})
export class AppComponent implements AfterViewInit, OnInit {
@@ -1,7 +1,8 @@
// #docregion
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html'
templateUrl: 'app.component.html'
})
export class AppComponent { }
@@ -272,6 +272,7 @@ export class ExternalTemplateComponent implements OnInit {
export class InnerCompWithExternalTemplateComponent { }
@Component({
moduleId: module.id,
selector: 'bad-template-comp',
templateUrl: 'non-existant.html'
})
@@ -5,9 +5,10 @@ import { Hero } from '../model';
// #docregion component
@Component({
moduleId: module.id,
selector: 'dashboard-hero',
templateUrl: 'app/dashboard/dashboard-hero.component.html',
styleUrls: ['app/dashboard/dashboard-hero.component.css']
templateUrl: 'dashboard-hero.component.html',
styleUrls: [ 'dashboard-hero.component.css' ]
})
export class DashboardHeroComponent {
@Input() hero: Hero;
@@ -5,9 +5,10 @@ import { Router } from '@angular/router';
import { Hero, HeroService } from '../model';
@Component({
moduleId: module.id,
selector: 'app-dashboard',
templateUrl: 'app/dashboard/dashboard.component.html',
styleUrls: ['app/dashboard/dashboard.component.css']
templateUrl: 'dashboard.component.html',
styleUrls: [ 'dashboard.component.css' ]
})
export class DashboardComponent implements OnInit {
@@ -9,9 +9,10 @@ import { HeroDetailService } from './hero-detail.service';
// #docregion prototype
@Component({
moduleId: module.id,
selector: 'app-hero-detail',
templateUrl: 'app/hero/hero-detail.component.html',
styleUrls: ['app/hero/hero-detail.component.css'],
templateUrl: 'hero-detail.component.html',
styleUrls: ['hero-detail.component.css' ],
providers: [ HeroDetailService ]
})
export class HeroDetailComponent implements OnInit {
@@ -4,9 +4,10 @@ import { Router } from '@angular/router';
import { Hero, HeroService } from '../model';
@Component({
moduleId: module.id,
selector: 'app-heroes',
templateUrl: 'app/hero/hero-list.component.html',
styleUrls: ['app/hero/hero-list.component.css']
templateUrl: 'hero-list.component.html',
styleUrls: [ 'hero-list.component.css' ]
})
export class HeroListComponent implements OnInit {
heroes: Promise<Hero[]>;
@@ -7,8 +7,9 @@ import { HeroService } from './hero.service';
// #enddocregion imports
@Component({
moduleId: module.id,
selector: 'my-dashboard',
templateUrl: 'app/dashboard.component.html'
templateUrl: 'dashboard.component.html'
})
// #docregion component
export class DashboardComponent implements OnInit {
@@ -9,12 +9,13 @@ import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'my-dashboard',
// #docregion templateUrl
templateUrl: 'app/dashboard.component.html',
templateUrl: 'dashboard.component.html',
// #enddocregion templateUrl
// #docregion css
styleUrls: ['app/dashboard.component.css']
styleUrls: [ 'dashboard.component.css' ]
// #enddocregion css
})
// #docregion component
@@ -7,11 +7,12 @@ import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'my-hero-detail',
// #docregion templateUrl
templateUrl: 'app/hero-detail.component.html',
templateUrl: 'hero-detail.component.html',
// #enddocregion templateUrl, v2
styleUrls: ['app/hero-detail.component.css']
styleUrls: [ 'hero-detail.component.css' ]
// #docregion v2
})
// #docregion implement
@@ -8,10 +8,11 @@ import { HeroService } from './hero.service';
// #docregion renaming, metadata
@Component({
moduleId: module.id,
selector: 'my-heroes',
// #enddocregion renaming
templateUrl: 'app/heroes.component.html',
styleUrls: ['app/heroes.component.css']
templateUrl: 'heroes.component.html',
styleUrls: [ 'heroes.component.css' ]
// #docregion renaming
})
// #enddocregion metadata
@@ -6,9 +6,10 @@ import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'my-dashboard',
templateUrl: 'app/dashboard.component.html',
styleUrls: ['app/dashboard.component.css']
templateUrl: 'dashboard.component.html',
styleUrls: [ 'dashboard.component.css' ]
})
// #enddocregion search
export class DashboardComponent implements OnInit {
@@ -6,9 +6,10 @@ import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'my-hero-detail',
templateUrl: 'app/hero-detail.component.html',
styleUrls: ['app/hero-detail.component.css']
templateUrl: 'hero-detail.component.html',
styleUrls: [ 'hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
hero: Hero;
@@ -9,9 +9,10 @@ import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';
@Component({
moduleId: module.id,
selector: 'hero-search',
templateUrl: 'app/hero-search.component.html',
styleUrls: ['app/hero-search.component.css'],
templateUrl: 'hero-search.component.html',
styleUrls: [ 'hero-search.component.css' ],
providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
@@ -6,9 +6,10 @@ import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'my-heroes',
templateUrl: 'app/heroes.component.html',
styleUrls: ['app/heroes.component.css']
templateUrl: 'heroes.component.html',
styleUrls: [ 'heroes.component.css' ]
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
@@ -8,8 +8,9 @@ import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
// #docregion initialclass
@Component({
moduleId: module.id,
selector: 'phone-detail',
templateUrl: 'phone-detail/phone-detail.template.html',
templateUrl: 'phone-detail.template.html',
// #enddocregion initialclass
pipes: [ CheckmarkPipe ]
// #docregion initialclass
@@ -4,8 +4,9 @@ import { Component } from '@angular/core';
import { Phone, PhoneData } from '../core/phone/phone.service';
@Component({
moduleId: module.id,
selector: 'phone-list',
templateUrl: 'phone-list/phone-list.template.html'
templateUrl: 'phone-list.template.html'
})
export class PhoneListComponent {
phones: PhoneData[];
@@ -6,8 +6,9 @@ import { Phone, PhoneData } from '../core/phone/phone.service';
import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
@Component({
moduleId: module.id,
selector: 'phone-detail',
templateUrl: 'phone-detail/phone-detail.template.html',
templateUrl: 'phone-detail.template.html',
pipes: [ CheckmarkPipe ]
})
export class PhoneDetailComponent {
@@ -5,8 +5,9 @@ import { RouterLink } from '@angular/router-deprecated';
import { Phone, PhoneData } from '../core/phone/phone.service';
@Component({
moduleId: module.id,
selector: 'phone-list',
templateUrl: 'phone-list/phone-list.template.html',
templateUrl: 'phone-list.template.html',
directives: [ RouterLink ]
})
// #enddocregion top
@@ -2,7 +2,8 @@
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app/app.component.html'
templateUrl: 'app.component.html'
})
export class AppComponent { }
+2
View File
@@ -166,6 +166,8 @@ figure.image-display
1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
1. The `moduleId` property sets the base for module-relative URLs such as the `templateUrl`.
1. The `templateUrl` property points to a separate file for template HTML called `hero_form_component.html`.
1. We defined dummy data for `model` and `powers`, as befits a demo.
@@ -36,13 +36,6 @@
"intro": "Techniques for Dependency Injection"
},
"dynamic-form-deprecated": {
"title": "Dynamic Forms",
"intro": "Render dynamic forms with NgFormModel",
"basics": true,
"hide": true
},
"dynamic-form": {
"title": "Dynamic Forms",
"intro": "Render dynamic forms with FormGroup"
@@ -1,149 +0,0 @@
include ../_util-fns
.alert.is-important
:marked
This cookbook is using the deprecated forms API, which is disabled as of RC5, thus this sample only works up to RC4.
We have created a new version of this cookbook using the new API <a href='/docs/ts/latest/cookbook/dynamic-form.html'>here</a>.
:marked
We can't always justify the cost and time to build handcrafted forms,
especially if we'll need a great number of them, they're similar to each other, and they change frequently
to meet rapidly changing business and regulatory requirements.
It may be more economical to create the forms dynamically, based on metadata that describe the business object model.
In this cookbook we show how to use `ngFormModel` to dynamically render a simple form with different control types and validation.
It's a primitive start.
It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience.
All such greatness has humble beginnings.
In our example we use a dynamic form to build an online application experience for heroes seeking employment.
The agency is constantly tinkering with the application process.
We can create the forms on the fly *without changing our application code*.
<a id="toc"></a>
:marked
## Table of contents
[Question Model](#object-model)
[Form Component](#form-component)
[Questionnaire Metadata](#questionnaire-metadata)
[Dynamic Template](#dynamic-template)
:marked
**See the <live-example name="cb-dynamic-form-deprecated"></live-example>**.
.l-main-section
<a id="object-model"></a>
:marked
## Question Model
The first step is to define an object model that can describe all scenarios needed by the form functionality.
The hero application process involves a form with a lot of questions.
The "question" is the most fundamental object in the model.
We have created `QuestionBase` as the most fundamental question class.
+makeExample('cb-dynamic-form-deprecated/ts/app/question-base.ts','','app/question-base.ts')
:marked
From this base we derived two new classes in `TextboxQuestion` and `DropdownQuestion` that represent Textbox and Dropdown questions.
The idea is that the form will be bound to specific question types and render the appropriate controls dynamically.
`TextboxQuestion` supports multiple html5 types like text, email, url etc via the `type` property.
+makeExample('cb-dynamic-form-deprecated/ts/app/question-textbox.ts',null,'app/question-textbox.ts')(format='.')
:marked
`DropdownQuestion` presents a list of choices in a select box.
+makeExample('cb-dynamic-form-deprecated/ts/app/question-dropdown.ts',null,'app/question-dropdown.ts')(format='.')
:marked
Next we have defined `QuestionControlService`, a simple service for transforming our questions to an ngForm control group.
In a nutshell, the control group consumes the metadata from the question model and allows us to specify default values and validation rules.
+makeExample('cb-dynamic-form-deprecated/ts/app/question-control.service.ts',null,'app/question-control.service.ts')(format='.')
<a id="form-component"></a>
:marked
## Question form components
Now that we have defined the complete model we are ready to create components to represent the dynamic form.
:marked
`DynamicFormComponent` is the entry point and the main container for the form.
+makeTabs(
`cb-dynamic-form-deprecated/ts/app/dynamic-form.component.html,
cb-dynamic-form-deprecated/ts/app/dynamic-form.component.ts`,
null,
`dynamic-form.component.html,
dynamic-form.component.ts`
)
:marked
It presents a list of questions, each question bound to a `<df-question>` component element.
The `<df-question>` tag matches the `DynamicFormQuestionComponent`,
the component responsible for rendering the details of each _individual_ question based on values in the data-bound question object.
+makeTabs(
`cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.html,
cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.ts`,
null,
`dynamic-form-question.component.html,
dynamic-form-question.component.ts`
)
:marked
Notice this component can present any type of question in our model.
We only have two types of questions at this point but we can imagine many more.
The `ngSwitch` determines which type of question to display.
In both components we're relying on Angular's **ngFormModel** to connect the template HTML to the
underlying control objects, populated from the question model with display and validation rules.
<a id="questionnaire-metadata"></a>
:marked
## Questionnaire data
:marked
`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
The set of questions we have defined for the job application is returned from the `QuestionService`.
In a real app we'd retrieve these questions from storage.
The key point is that we control the hero job application questions entirely through the objects returned from `QuestionService`.
Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array.
+makeExample('cb-dynamic-form-deprecated/ts/app/question.service.ts','','app/question.service.ts')
:marked
Finally, we display an instance of the form in the `AppComponent` shell.
+makeExample('cb-dynamic-form-deprecated/ts/app/app.component.ts','','app.component.ts')
<a id="dynamic-template"></a>
:marked
## Dynamic Template
Although in this example we're modelling a job application for heroes, there are no references to any specific hero question
outside the objects returned by `QuestionService`.
This is very important since it allows us to repurpose the components for any type of survey
as long as it's compatible with our *question* object model.
The key is the dynamic data binding of metadata used to render the form
without making any hardcoded assumptions about specific questions.
In addition to control metadata, we are also adding validation dynamically.
The *Save* button is disabled until the form is in a valid state.
When the form is valid, we can click *Save* and the app renders the current form values as JSON.
This proves that any user input is bound back to the data model.
Saving and retrieving the data is an exercise for another time.
:marked
The final form looks like this:
figure.image-display
img(src="/resources/images/cookbooks/dynamic-form/dynamic-form.png" alt="Dynamic-Form")
:marked
[Back to top](#top)
@@ -252,12 +252,14 @@ block ts-decorator
Here are a few of the possible `@Component` configuration options:
:marked
- `moduleId: module.id`: sets the base for module-relative loading of the `templateUrl`.
- `selector`: CSS selector that tells Angular to create and insert an instance of this component
where it finds a `<hero-list>` tag in *parent* HTML.
For example, if an app's HTML contains `<hero-list></hero-list>`, then
Angular inserts an instance of the `HeroListComponent` view between those tags.
- `templateUrl`: address of this component's HTML template, shown [above](#templates).
- `templateUrl`: module-relative address of this component's HTML template, shown [above](#templates).
- `directives`: !{_array} of the components or directives that *this* template requires.
In the last line of `hero-list.component.html`, Angular inserts a `HeroDetailComponent`
+2
View File
@@ -141,6 +141,8 @@ code-example(format="").
1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
1. The `moduleId: module.id` property sets the base for module-relative loading of the `templateUrl`.
1. The `templateUrl` property points to a separate file for the template HTML called `hero-form.component.html`.
1. We defined dummy data for `model` and `powers` as befits a demo.
+3 -10
View File
@@ -370,15 +370,9 @@ block redirect-vs-use-as-default
Replace the `template` metadata with a `templateUrl` property that points to a new
template file.
+makeExcerpt('app/dashboard.component.ts', 'templateUrl')
Set the `moduleId` property to `module.id` for module-relative loading of the `templateUrl`.
.l-sub-section
block templateUrl-path-resolution
:marked
We specify the path _all the way back to the application root_ &mdash;
<span if-docs="ts">`app/` in this case &mdash;</span>
because Angular doesn't support relative paths _by default_.
We _can_ switch to [component-relative paths](../cookbook/component-relative-paths.html) if we prefer.
+makeExcerpt('app/dashboard.component.ts', 'templateUrl')
:marked
Create that file with this content:
@@ -718,13 +712,12 @@ figure.image-display
1. *Cut-and-paste* the template contents into a new <span ngio-ex>heroes.component.html</span> file.
1. *Cut-and-paste* the styles contents into a new <span ngio-ex>heroes.component.css</span> file.
1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files.
1. *Set* the `moduleId` property to `module.id` so that 'templateUrl` and `styleUrls` are relative to the component.
.l-sub-section
:marked
The `styleUrls` property is !{_an} !{_array} of style file names (with paths).
We could list multiple style files from different locations if we needed them.
<span if-docs="ts">As with `templateUrl`, we must specify the path _all the way
back to the application root_.</span>
block heroes-component-cleanup
//- Only relevant for Dart.