Developer
Reviewing the HTTP Angular Tutorial this morning, I was really confused how the sample “Web API” was working in the sample plunker.
It started out as:
search(term: string): Observable<Hero[]> {
return this.http
.get(`app/heroes/?name=${term}`)
.map((r: Response) => r.json().data as Hero[]);
app/heroes/?name=${term}
is clearly the API call, but where is the function that processes it? I assumed that this was in HeroesComponent, since app.routing.ts
defines the heroes
route.
{
path: 'heroes',
component: HeroesComponent
}
But why would the API be in a component class, that makes no sense! Moreover, the only likely function,
getHeroes(): void {
this.heroService
.getHeroes()
.then(heroes => this.heroes = heroes);
}
doesn’t include any parameters, and a console.log()
very clearly showed this function is not called during search operations.
I was close to just assuming there was some invisible api I couldn’t see, when I re-read the tutorial and noticed something about the InMemoryDataService
:
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
createDb() {
let heroes = [
{id: 11, name: 'Mr. Nice'},
{id: 12, name: 'Narco'},
{id: 13, name: 'Bombasto'},
{id: 14, name: 'Celeritas'},
{id: 15, name: 'Magneta'},
{id: 16, name: 'RubberMan'},
{id: 17, name: 'Dynama'},
{id: 18, name: 'Dr IQ'},
{id: 19, name: 'Magma'},
{id: 20, name: 'Tornado'}
];
return {heroes};
}
}
the return name is {heroes}
. What if I changed that to {heroesx}
? Sure enough, the app/heroes
call failed. I then tried changing the service url in hero.service.ts
to
private heroesUrl = 'app/heroesx';
And the service worked again. That’s pretty interesting behavior, but certainly not intuitive…
The next question was how the name filter worked
.get(`app/heroes/?name=${term}`)
As I didn’t see any functions for that. Well it turns out this is also a feature of the InMemoryDataService
. You can pass a filter for any property of the json, so this will work as well:
.get(`app/heroes/?id=${term}`)
Going over the Angular2 docs on forms, and decided to bring forward my 1.x simple forms demo
It ports over fairly well, but a significant number of changes needed to be made. Honestly it seems like it would take an enormous effort to convert an existing 1.x application to Angular2.
ng-model
-> [(ngModel)]
ng-class
-> [ngClass]
ng-submit
-> (ngSubmit)
ng-show
-> [hidden]
(could have also used *ngIf
)ng-maxlength
-> [maxlength]
ng-disabled
-> [disabled]
filter
, I created a pipe
ManufacturerService
with a type
class and a mock
LookupService
for the list of statesangular.copy()
does not have a direct replacement in Angular2. Object.assign()
works well as long as a deep copy is not needed, which isn’t the case here (Manufacturer doesn’t contain collections, just simple types)new
button is clicked)currency
filter will not work with an empty string. In edit mode, blanking out the <input>
causes <span class="displayValue" [ngClass]="pageMode">{{manufacturer.creditLimit | currency }}</span>
to raise an exception. The workaround is to set the <input type="number"
manufacturerForm
properly from the simple-edit-form.component.ts
.
manufacturerForm.valid
is available, but in the component.ts
it is undefinedmanufacturerForm.checkValidity()
valid
property as accessible in component.ts
, but I didn’t get this to work#manufacturerForm="ngForm"
inside the <form>
id
, name
attributes, needed to set #nameInput="ngModel"
inside the <input>
’s using validation.nameInput.errors.required
was giving undefined errors. It looks like you always need to check for valid
first, as the errors
collection is only defined when errors are present: [hidden]="nameInput.valid || !nameInput.errors.required"
The ||
causes a short-circuit evaluation in the expression. If you remove the nameInput.valid
from the example below you can repro this issue.Had a good one today. Someone asked me at work why a file upload wasn’t working on an old ASP intranet site. To be clear, the site doesn’t actually upload a file, it merely stores a network share/path to the file, and presents a file://
as a clickable link on a webpage. Bad design yes, but good enough for it’s purpose.
The site worked in IE, but not in chrome, not too suprising, but I didn’t know what the issue was. I quickly threw up a fiddle, and reproduced the behavior in chrome/firefox. Here’s what the different browsers displayed:
With the same HTML:
<input id="fileInput" type=file name=attachment maxlength=200 size=40>
I did a little digging, seeing what I could extract from the file, when I noticed something curious. document.getElementById("fileInput").value
was returning C:\fakepath\myFile.txt
Ummm, what?
Well it turns out that the HTML5 folks really don’t want the browser mucking about with the local file system. This actually goes back several years, according to this post from 2009. It’s not a big deal though, I’ll just re-write that intranet app…
Thankfully it will work on IE, as long as this easy-to-configure setting is in place:
The funny part is, if this is set to disable
, the <input>
will show the path as c:\temp\myFile.txt
, but the actual value will be c:\fakepath\myFile.txt
.
Frankly the better solution would be to simply return the file name without a real or fake path. Firefox does this currently (try it out in the fiddle to see for yourself). I can understand the need to deprecate a feature, but if you are going to break something, don’t return a quasi-real result.
Looking at adding cookies to an Angular2 demo the other day, and found out the $cookie service is not implemented natively in Angular2. Doing some quick googling, found a decent implementation: https://github.com/salemdar/angular2-cookie .
To use, you simply need to import the CookieService
inside of app.module
, making it available to other components.
Interacting with the cookies is pretty simple:
this._cookieService.put("cookieSimple", "A Simple Cookie");
this._cookieService.putObject("cookieObject", {option1:true, option2:1, option3:"default"});
this._cookieService.put("cookieWithOptions", "Expires in a long time", {expires:"Mon, 30 Jun 2290 00:00:00 GMT"});
this.cookieSimpleValue = this._cookieService.get("cookieSimple");
this.cookieObjectValue = this._cookieService.get("cookieObject"); // or getObject()
this.cookieWithOptionsValue = this._cookieService.get("cookieWithOptions");
plunker demo:
Going over the documentation for Angular2 pipes today.
Pipes have changed since angular 1. Most noticeable is the lack of filter/sorting pipes. The reason given for their removal was speed. While they worked well enough for demo applications, real-world usage often caused speed issues. At my company we noticed this with lists only a few hundred items long.
Angular comes with a few standard pipes
{{ myValue | date}}
You pass parameters to the pipe with a :
{{myValue | date:"shortDate"}}
The currency pipe requires multiple parameters, seperated by :
{{myValue | currency:"USD":true}}
It’s simple to chain pipes:
{{ myValue | uppercase | lowercase}}
If the built-in pipes do not meet your needs, you will need to process the data in the component, or create a custom pipe. The angular team recommends handling expensive operations i.e. filtering in the component, instead of a pipe.
To create the custom pipe you’ll need to:
<pipename>.pipe.ts
app.module.ts
, specifying both as an import and a declaration.Here is a basic pipe implementation:
import { Pipe, PipeTransform } from '@angular/core';
// Take Name e.g. Bill Gates and return Gates, Bill
@Pipe({name: 'LastNameFirst',
pure: true
})
export class LastNameFirstPipe implements PipeTransform {
transform(value: string): string {
return value.substring(value.indexOf(' ') + 1) + ", " + value.substring(0, value.indexOf(' '));
}
}
Notice the pure
attribute. Pipes in angular can be marked as pure
or unpure
Pure pipes:
{{customer.name | uppercase}}
a change to name would not cause the filter to be re-evaluated, since this is inside the customer object. This is for performance reasons.Impure Pipes
pure
set to false
.plunker demo: