Upgrading to Angular 4 post ng-conf
Just came back from ng-conf and was fired up to deploy the goodness of Angular 4. First up, the website for my Los Angeles catering company.
The Promise
Coming from the conference, I had a sense that the upgrade was almost all good with almost no effort, given the team’s focus on improving speed while maintaining backward compatibility with Angular 2.
I knew I had to bump the version on Angular (4), the CLI (1), and Angular Material (beta.3)
I’m super excited about where the framework is going, especially with Rob Wormald’s talk on where PWAs are going with the use of Service Workers and the ability to create AMP pages.
The Experience
I had a few moments of doubt but appear to have come away with a fairly straightforward upgrade experience…however there were quite a few breaking changes.
- New meta service: The previous hacks for meta tags were replaced by a nicely designed Meta service. Didn’t mind the upgrade and glad I had it all consolidated in a service I could change over fairly quickly.
- Deep imports are gone: The meta service doesn’t handle link tags in the header. I kinda freaked out about this, as my rel=canonical tags all broke. Given that canonical tags are, well, canonical I thought this might keep me stuck with a hacked import of getDom() from platform-browser in Angular 2. Mike Brocchi was amazingly helpful in helping me figure out the issue (the switch from allowing deep imports), Jeff Cross helped me figure out that this was a known and under discussion issue, and the amazing Mark Pieszak already had a hack up to address the issue
... (from whatever.component.ts)
constructor(linkService: LinkService) {
this.linkService.addTag( { rel: 'canonical', href: 'http://blogs.example.com/blah/nice' } );
this.linkService.addTag( { rel: 'alternate', hreflang: 'es', href: 'http://es.example.com/' } );
}/*
* -- LinkService -- [Temporary]
* @MarkPieszak
*
* Similar to Meta service but made to handle <link> creation for SEO purposes
* -- NOTE: Soon there will be an overall DocumentService within Angular that handles Meta/Link everything
*/
import { Injectable, Optional, RendererFactory2, ViewEncapsulation, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/platform-browser';
@Injectable()
export class LinkService {
constructor(
private rendererFactory: RendererFactory2,
@Inject(DOCUMENT) private document
) {
}
/**
* Inject the State into the bottom of the <head>
*/
addTag(tag: LinkDefinition, forceCreation?: boolean) {
try {
const renderer = this.rendererFactory.createRenderer(this.document, {
id: '-1',
encapsulation: ViewEncapsulation.None,
styles: [],
data: {}
});
const link = renderer.createElement('link');
const head = this.document.head;
const selector = this._parseSelector(tag);
if (head === null) {
throw new Error('<head> not found within DOCUMENT.');
}
Object.keys(tag).forEach((prop: string) => {
return renderer.setAttribute(link, prop, tag[prop]);
});
// [TODO]: get them to update the existing one (if it exists) ?
renderer.appendChild(head, link);
} catch (e) {
console.error('Error within linkService : ', e);
}
}
private _parseSelector(tag: LinkDefinition): string {
// Possibly re-work this
const attr: string = tag.rel ? 'rel' : 'hreflang';
return `${attr}="${tag[attr]}"`;
}
}
export declare type LinkDefinition = {
charset?: string;
crossorigin?: string;
href?: string;
hreflang?: string;
media?: string;
rel?: string;
rev?: string;
sizes?: string;
target?: string;
type?: string;
} & {
[prop: string]: string;
};
I had a few issues with it given the structure of how I build my canonical tags as users clicked on pages (this one adds the tag to the bottom of the page) so I seem to have gotten it working by removing the old canonical link first and then adding the update per above
removeCanonicalLink() {
try {
const renderer = this.rendererFactory.createRenderer(this.document, {
id: '-1',
encapsulation: ViewEncapsulation.None,
styles: [],
data: {}
});
const canonical = document.querySelector("link[rel='canonical']")
const head = this.document.head;
if (head === null) {
throw new Error('<head> not found within DOCUMENT.');
}
if (!!canonical) {
renderer.removeChild(head, canonical);
}
} catch (e) {
console.error('Error within linkService : ', e);
}
}
The deep import issue also means that the Angular Material module for beta.3 needs to be broken up into its component parts per the changelog (don’t think this has quite hit the main docs)
3. Everything broke on ng serve — prod: The new implementation of AoT is both automatic and sweet! (a good number of my module chunks are 30–50% smaller than before). That said, the requirement to explicitly use public on all properties referenced in the template meant that I had a loooooooooooooooooooooong string of red lines in my terminal for ng serve — prod that didn’t show up for ng serve
Property ‘property’ is private and only accessible within class ‘WhateverComponent’
So I had to make all my properties, Inputs, etc explicitly public on creating the production build. Be prepared to make the fixes (and have some shortcuts so you’re automating this as much as possible)
a) @Input() public property
b) @Output() public property
c) b) public property
The conclusion
All in all, the Angular core team is doing an amazing job and I’m really excited about where the Angular community and platform is going. But expect a few things to break as you upgrade to get all the goodness of Angular (v4)